--- /dev/null
+ 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
+
--- /dev/null
+#ChangeLog
--- /dev/null
+
+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]
+
--- /dev/null
+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.
+
--- /dev/null
+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.
--- /dev/null
+# 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
+
--- /dev/null
+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.
--- /dev/null
+#!/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 "---------------------------------------------"
--- /dev/null
+#!/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';
+
+
--- /dev/null
+# 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
--- /dev/null
+#!/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;
+
+
+
--- /dev/null
+# 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([----------------------------------------------------------------------])
--- /dev/null
+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;
+
--- /dev/null
+<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">
+
+<oils:domainObjectAttr value="1" name="protocol"/>
+
+ </pre>
+
+ <h2> threadTrace Element </h2>
+
+ <pre style="border: solid thin blue; margin: 0% 10% 0% 10%; padding-left: 50px">
+
+<oils:domainObjectAttr value="1" name="threadTrace"/>
+
+ </pre>
+
+ <h2> CONNECT Message </h2>
+
+ <pre style="border: solid thin blue; margin: 0% 10% 0% 10%; padding-left: 50px">
+
+<oils:domainObject name="oilsMessage">
+ <oils:domainObjectAttr value="CONNECT" name="type"/>
+ <oils:domainObjectAttr value="1" name="threadTrace"/>
+ <oils:domainObjectAttr value="1" name="protocol"/>
+</oils:domainObject>
+
+ </pre>
+
+
+ <h2> DISCONNECT Message </h2>
+
+ <pre style="border: solid thin blue; margin: 0% 10% 0% 10%; padding-left: 50px">
+
+<oils:domainObject name="oilsMessage">
+ <oils:domainObjectAttr value="DISCONNECT" name="type"/>
+ <oils:domainObjectAttr value="0" name="threadTrace"/>
+ <oils:domainObjectAttr value="1" name="protocol"/>
+</oils:domainObject>
+
+ </pre>
+
+ <h2> STATUS Message </h2>
+
+ <pre style="border: solid thin blue; margin: 0% 10% 0% 10%; padding-left: 50px">
+
+<oils:domainObject name="oilsMessage">
+ <oils:domainObjectAttr value="STATUS" name="type"/>
+ <oils:domainObjectAttr value="0" name="threadTrace"/>
+ <oils:domainObjectAttr value="1" name="protocol"/>
+ <oils:domainObject name="oilsConnectStatus">
+ <oils:domainObjectAttr value="Connection Successful" name="status"/>
+ <oils:domainObjectAttr value="200" name="statusCode"/>
+ </oils:domainObject>
+</oils:domainObject>
+
+ </pre>
+
+ <h2> REQUEST Message </h2>
+
+ <pre style="border: solid thin blue; margin: 0% 10% 0% 10%; padding-left: 50px">
+
+<oils:domainObject name="oilsMessage">
+ <oils:domainObjectAttr value="REQUEST" name="type"/>
+ <oils:domainObjectAttr value="4" name="threadTrace"/>
+ <oils:domainObjectAttr value="1" name="protocol"/>
+ <oils:domainObject name="oilsMethod">
+ <oils:domainObjectAttr value="mult" name="method"/>
+ <oils:params>[1, 2]</oils:params>
+ </oils:domainObject>
+</oils:domainObject>
+
+ </pre>
+
+ <h2> RESULT Message </h2>
+
+ <pre style="border: solid thin blue; margin: 0% 10% 0% 10%; padding-left: 50px">
+
+<oils:domainObject name="oilsMessage">
+ <oils:domainObjectAttr value="RESULT" name="type"/>
+ <oils:domainObjectAttr value="4" name="threadTrace"/>
+ <oils:domainObjectAttr value="1" name="protocol"/>
+ <oils:domainObject name="oilsResult">
+ <oils:domainObjectAttr value="OK" name="status"/>
+ <oils:domainObjectAttr value="200" name="statusCode"/>
+ <oils:domainObject name="oilsScalar">2</oils:domainObject>
+ </oils:domainObject>
+</oils:domainObject>
+
+ </pre>
+
+
+ </body>
+
+</html>
+
+
--- /dev/null
+<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>
--- /dev/null
+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?
+
--- /dev/null
+#!/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
+}
+
--- /dev/null
+<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>
+
--- /dev/null
+<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>
+
--- /dev/null
+<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>
+
--- /dev/null
+<?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>
+
--- /dev/null
+#!/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";
+
+
--- /dev/null
+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
--- /dev/null
+// 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;
+}
+
--- /dev/null
+<?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>
--- /dev/null
+<?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>
+
--- /dev/null
+<?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>
+
--- /dev/null
+// 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(); }
+
+
--- /dev/null
+<?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>
--- /dev/null
+<?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>
+
--- /dev/null
+<!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">
+
--- /dev/null
+<?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>
+
--- /dev/null
+@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; }
--- /dev/null
+#!/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";
+
--- /dev/null
+<?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>
--- /dev/null
+<?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>
--- /dev/null
+#!/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();
+
+
--- /dev/null
+<?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>
--- /dev/null
+<?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>
--- /dev/null
+#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
--- /dev/null
+/* --- 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 */
+
--- /dev/null
+/*
+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
--- /dev/null
+#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
--- /dev/null
+#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();
+
+
--- /dev/null
+#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
--- /dev/null
+#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
--- /dev/null
+/*
+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();
--- /dev/null
+#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
--- /dev/null
+/*
+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
--- /dev/null
+/*
+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
+
--- /dev/null
+#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
--- /dev/null
+/*
+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
+
+
--- /dev/null
+#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
--- /dev/null
+#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
--- /dev/null
+#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
--- /dev/null
+#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
+
--- /dev/null
+#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
--- /dev/null
+#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
--- /dev/null
+#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 );
+
+
--- /dev/null
+// 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
+
--- /dev/null
+#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
--- /dev/null
+#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
--- /dev/null
+#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
--- /dev/null
+#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
--- /dev/null
+// ---------------------------------------------------------------------------------
+// Manages the Jabber session. Data is taken from the TCP object and pushed into
+// a SAX push parser as it arrives. When key Jabber documetn elements are met,
+// logic ensues.
+// ---------------------------------------------------------------------------------
+#include <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
--- /dev/null
+/*
+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
--- /dev/null
+#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
--- /dev/null
+# 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)
+
--- /dev/null
+# 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
--- /dev/null
+#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;
+}
+
+
+
--- /dev/null
+#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;
+}
+
+
+
--- /dev/null
+#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;
+}
+
+
+
--- /dev/null
+#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;
+
+}
--- /dev/null
+<?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" -->'/> 
+ 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> 
+ 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> 
+ <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" -->'/> 
+ 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> 
+ 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> 
+ <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" --> > 0">
+ <span>
+ <a>
+ <xsl:attribute name="href">docgen.xsl?service=<!--#echo var="service" -->&all=<!--#echo var="all" -->&param="<!--#echo var="param" -->"&limit=<!--#echo var="limit" -->&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" -->&all=<!--#echo var="all" -->&param="<!--#echo var="param" -->"&limit=<!--#echo var="limit" -->&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¶m="$param"¶m=$limit¶m=$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¶m=$limit¶m=$offset' -->
+ <!--#endif -->
+ <!--#endif -->
+
+
+</xsl:stylesheet>
+
--- /dev/null
+# 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
--- /dev/null
+#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;
+}
+
+*/
+
+
--- /dev/null
+#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
--- /dev/null
+#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,
+};
+
+
+
+
--- /dev/null
+#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,
+};
+
+
+
+
--- /dev/null
+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
+
+
--- /dev/null
+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"
+
--- /dev/null
+# ----------------------------------------------------------------
+# 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
--- /dev/null
+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);
+ }
+ }
+}
+
--- /dev/null
+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;
+ }
+}
+
+
--- /dev/null
+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;
+ }
+
+}
+
--- /dev/null
+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);
+ }
+}
+
--- /dev/null
+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();
+ }
+}
+
--- /dev/null
+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;
+ }
+}
--- /dev/null
+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;
+ }
+
+}
+
--- /dev/null
+package org.opensrf;
+
+/**
+ * Models an OpenSRF server session.
+ */
+public class ServerSession extends Session {
+}
+
--- /dev/null
+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;
+ }
+}
--- /dev/null
+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);
+ }
+}
+
--- /dev/null
+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) {
+ }
+}
--- /dev/null
+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;
+ }
+}
+
+
--- /dev/null
+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();
+ }
+}
+
--- /dev/null
+package org.opensrf.net.xmpp;
+
+/**
+ * Used for XMPP stream/authentication errors
+ */
+public class XMPPException extends Exception {
+ public XMPPException(String info) {
+ super(info);
+ }
+}
--- /dev/null
+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("<");
+ break;
+ case '>':
+ sb.append(">");
+ break;
+ case '&':
+ sb.append("&");
+ break;
+ default:
+ sb.append(c);
+ }
+ }
+ }
+}
+
+
--- /dev/null
+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;
+ }
+ }
+}
+
+
+
+
--- /dev/null
+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) {}
+ }
+}
+
--- /dev/null
+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();
+ }
+}
+
+
+
--- /dev/null
+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"));
+ }
+}
+
+
--- /dev/null
+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();
+ }
+}
+
+
+
--- /dev/null
+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]));
+ }
+}
--- /dev/null
+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());
+ }
+}
--- /dev/null
+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");
+ }
+}
--- /dev/null
+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();
+ }
+ }
+}
--- /dev/null
+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);
+ }
+}
--- /dev/null
+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();
+ }
+}
+
+
+
--- /dev/null
+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());
+ }
+}
--- /dev/null
+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();
+ }
+ }
+}
+
+
--- /dev/null
+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());
+ }
+ }
+}
+
+
+
+
+
--- /dev/null
+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[]{}));
+ }
+}
+
--- /dev/null
+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();
+ }
+}
+
--- /dev/null
+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);
+ }
+}
--- /dev/null
+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) {}
+ }
+}
--- /dev/null
+package org.opensrf.util;
+/**
+ * Used to indicate JSON parsing errors
+ */
+public class JSONException extends Exception {
+ public JSONException(String s) {
+ super(s);
+ }
+}
--- /dev/null
+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;
+ }
+}
+
+
+
--- /dev/null
+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);
+ }
+}
+
+
+
--- /dev/null
+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));
+ }
+}
+
--- /dev/null
+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();
+ }
+}
--- /dev/null
+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;
+ }
+}
+
+
--- /dev/null
+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);
+}
+
+
--- /dev/null
+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();
+ }
+ }
+}
+
--- /dev/null
+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]);
+ }
+}
+
+
+
--- /dev/null
+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;
+ }
+}
+
+
+
+
--- /dev/null
+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();
+ }
+}
+
+
--- /dev/null
+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;
+ }
+}
--- /dev/null
+// in case we run on an implimentation that doesn't have "undefined";
+var undefined;
+
+function Cast (obj, class_constructor) {
+ try {
+ if (eval(class_constructor + '["_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') + '"';
+ }
+
+}
--- /dev/null
+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;
+}
+
+
--- /dev/null
+/*
+ * A JavaScript implementation of the RSA Data Security, Inc. MD5 Message
+ * Digest Algorithm, as defined in RFC 1321.
+ * Version 2.1 Copyright (C) Paul Johnston 1999 - 2002.
+ * Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet
+ * Distributed under the BSD License
+ * See http://pajhome.org.uk/crypt/md5 for more info.
+ */
+
+/*
+ * Configurable variables. You may need to tweak these to be compatible with
+ * the server-side, but the defaults work in most cases.
+ */
+var hexcase = 0; /* hex output format. 0 - lowercase; 1 - uppercase */
+var b64pad = ""; /* base-64 pad character. "=" for strict RFC compliance */
+var chrsz = 8; /* bits per input character. 8 - ASCII; 16 - Unicode */
+
+/*
+ * These are the functions you'll usually want to call
+ * They take string arguments and return either hex or base-64 encoded strings
+ */
+function hex_md5(s){ return binl2hex(core_md5(str2binl(s), s.length * chrsz));}
+function b64_md5(s){ return binl2b64(core_md5(str2binl(s), s.length * chrsz));}
+function str_md5(s){ return binl2str(core_md5(str2binl(s), s.length * chrsz));}
+function hex_hmac_md5(key, data) { return binl2hex(core_hmac_md5(key, data)); }
+function b64_hmac_md5(key, data) { return binl2b64(core_hmac_md5(key, data)); }
+function str_hmac_md5(key, data) { return binl2str(core_hmac_md5(key, data)); }
+
+/*
+ * Perform a simple self-test to see if the VM is working
+ */
+function md5_vm_test()
+{
+ return hex_md5("abc") == "900150983cd24fb0d6963f7d28e17f72";
+}
+
+/*
+ * Calculate the MD5 of an array of little-endian words, and a bit length
+ */
+function core_md5(x, len)
+{
+ /* append padding */
+ x[len >> 5] |= 0x80 << ((len) % 32);
+ x[(((len + 64) >>> 9) << 4) + 14] = len;
+
+ var a = 1732584193;
+ var b = -271733879;
+ var c = -1732584194;
+ var d = 271733878;
+
+ for(var i = 0; i < x.length; i += 16)
+ {
+ var olda = a;
+ var oldb = b;
+ var oldc = c;
+ var oldd = d;
+
+ a = md5_ff(a, b, c, d, x[i+ 0], 7 , -680876936);
+ d = md5_ff(d, a, b, c, x[i+ 1], 12, -389564586);
+ c = md5_ff(c, d, a, b, x[i+ 2], 17, 606105819);
+ b = md5_ff(b, c, d, a, x[i+ 3], 22, -1044525330);
+ a = md5_ff(a, b, c, d, x[i+ 4], 7 , -176418897);
+ d = md5_ff(d, a, b, c, x[i+ 5], 12, 1200080426);
+ c = md5_ff(c, d, a, b, x[i+ 6], 17, -1473231341);
+ b = md5_ff(b, c, d, a, x[i+ 7], 22, -45705983);
+ a = md5_ff(a, b, c, d, x[i+ 8], 7 , 1770035416);
+ d = md5_ff(d, a, b, c, x[i+ 9], 12, -1958414417);
+ c = md5_ff(c, d, a, b, x[i+10], 17, -42063);
+ b = md5_ff(b, c, d, a, x[i+11], 22, -1990404162);
+ a = md5_ff(a, b, c, d, x[i+12], 7 , 1804603682);
+ d = md5_ff(d, a, b, c, x[i+13], 12, -40341101);
+ c = md5_ff(c, d, a, b, x[i+14], 17, -1502002290);
+ b = md5_ff(b, c, d, a, x[i+15], 22, 1236535329);
+
+ a = md5_gg(a, b, c, d, x[i+ 1], 5 , -165796510);
+ d = md5_gg(d, a, b, c, x[i+ 6], 9 , -1069501632);
+ c = md5_gg(c, d, a, b, x[i+11], 14, 643717713);
+ b = md5_gg(b, c, d, a, x[i+ 0], 20, -373897302);
+ a = md5_gg(a, b, c, d, x[i+ 5], 5 , -701558691);
+ d = md5_gg(d, a, b, c, x[i+10], 9 , 38016083);
+ c = md5_gg(c, d, a, b, x[i+15], 14, -660478335);
+ b = md5_gg(b, c, d, a, x[i+ 4], 20, -405537848);
+ a = md5_gg(a, b, c, d, x[i+ 9], 5 , 568446438);
+ d = md5_gg(d, a, b, c, x[i+14], 9 , -1019803690);
+ c = md5_gg(c, d, a, b, x[i+ 3], 14, -187363961);
+ b = md5_gg(b, c, d, a, x[i+ 8], 20, 1163531501);
+ a = md5_gg(a, b, c, d, x[i+13], 5 , -1444681467);
+ d = md5_gg(d, a, b, c, x[i+ 2], 9 , -51403784);
+ c = md5_gg(c, d, a, b, x[i+ 7], 14, 1735328473);
+ b = md5_gg(b, c, d, a, x[i+12], 20, -1926607734);
+
+ a = md5_hh(a, b, c, d, x[i+ 5], 4 , -378558);
+ d = md5_hh(d, a, b, c, x[i+ 8], 11, -2022574463);
+ c = md5_hh(c, d, a, b, x[i+11], 16, 1839030562);
+ b = md5_hh(b, c, d, a, x[i+14], 23, -35309556);
+ a = md5_hh(a, b, c, d, x[i+ 1], 4 , -1530992060);
+ d = md5_hh(d, a, b, c, x[i+ 4], 11, 1272893353);
+ c = md5_hh(c, d, a, b, x[i+ 7], 16, -155497632);
+ b = md5_hh(b, c, d, a, x[i+10], 23, -1094730640);
+ a = md5_hh(a, b, c, d, x[i+13], 4 , 681279174);
+ d = md5_hh(d, a, b, c, x[i+ 0], 11, -358537222);
+ c = md5_hh(c, d, a, b, x[i+ 3], 16, -722521979);
+ b = md5_hh(b, c, d, a, x[i+ 6], 23, 76029189);
+ a = md5_hh(a, b, c, d, x[i+ 9], 4 , -640364487);
+ d = md5_hh(d, a, b, c, x[i+12], 11, -421815835);
+ c = md5_hh(c, d, a, b, x[i+15], 16, 530742520);
+ b = md5_hh(b, c, d, a, x[i+ 2], 23, -995338651);
+
+ a = md5_ii(a, b, c, d, x[i+ 0], 6 , -198630844);
+ d = md5_ii(d, a, b, c, x[i+ 7], 10, 1126891415);
+ c = md5_ii(c, d, a, b, x[i+14], 15, -1416354905);
+ b = md5_ii(b, c, d, a, x[i+ 5], 21, -57434055);
+ a = md5_ii(a, b, c, d, x[i+12], 6 , 1700485571);
+ d = md5_ii(d, a, b, c, x[i+ 3], 10, -1894986606);
+ c = md5_ii(c, d, a, b, x[i+10], 15, -1051523);
+ b = md5_ii(b, c, d, a, x[i+ 1], 21, -2054922799);
+ a = md5_ii(a, b, c, d, x[i+ 8], 6 , 1873313359);
+ d = md5_ii(d, a, b, c, x[i+15], 10, -30611744);
+ c = md5_ii(c, d, a, b, x[i+ 6], 15, -1560198380);
+ b = md5_ii(b, c, d, a, x[i+13], 21, 1309151649);
+ a = md5_ii(a, b, c, d, x[i+ 4], 6 , -145523070);
+ d = md5_ii(d, a, b, c, x[i+11], 10, -1120210379);
+ c = md5_ii(c, d, a, b, x[i+ 2], 15, 718787259);
+ b = md5_ii(b, c, d, a, x[i+ 9], 21, -343485551);
+
+ a = safe_add(a, olda);
+ b = safe_add(b, oldb);
+ c = safe_add(c, oldc);
+ d = safe_add(d, oldd);
+ }
+ return Array(a, b, c, d);
+
+}
+
+/*
+ * These functions implement the four basic operations the algorithm uses.
+ */
+function md5_cmn(q, a, b, x, s, t)
+{
+ return safe_add(bit_rol(safe_add(safe_add(a, q), safe_add(x, t)), s),b);
+}
+function md5_ff(a, b, c, d, x, s, t)
+{
+ return md5_cmn((b & c) | ((~b) & d), a, b, x, s, t);
+}
+function md5_gg(a, b, c, d, x, s, t)
+{
+ return md5_cmn((b & d) | (c & (~d)), a, b, x, s, t);
+}
+function md5_hh(a, b, c, d, x, s, t)
+{
+ return md5_cmn(b ^ c ^ d, a, b, x, s, t);
+}
+function md5_ii(a, b, c, d, x, s, t)
+{
+ return md5_cmn(c ^ (b | (~d)), a, b, x, s, t);
+}
+
+/*
+ * Calculate the HMAC-MD5, of a key and some data
+ */
+function core_hmac_md5(key, data)
+{
+ var bkey = str2binl(key);
+ if(bkey.length > 16) bkey = core_md5(bkey, key.length * chrsz);
+
+ var ipad = Array(16), opad = Array(16);
+ for(var i = 0; i < 16; i++)
+ {
+ ipad[i] = bkey[i] ^ 0x36363636;
+ opad[i] = bkey[i] ^ 0x5C5C5C5C;
+ }
+
+ var hash = core_md5(ipad.concat(str2binl(data)), 512 + data.length * chrsz);
+ return core_md5(opad.concat(hash), 512 + 128);
+}
+
+/*
+ * Add integers, wrapping at 2^32. This uses 16-bit operations internally
+ * to work around bugs in some JS interpreters.
+ */
+function safe_add(x, y)
+{
+ var lsw = (x & 0xFFFF) + (y & 0xFFFF);
+ var msw = (x >> 16) + (y >> 16) + (lsw >> 16);
+ return (msw << 16) | (lsw & 0xFFFF);
+}
+
+/*
+ * Bitwise rotate a 32-bit number to the left.
+ */
+function bit_rol(num, cnt)
+{
+ return (num << cnt) | (num >>> (32 - cnt));
+}
+
+/*
+ * Convert a string to an array of little-endian words
+ * If chrsz is ASCII, characters >255 have their hi-byte silently ignored.
+ */
+function str2binl(str)
+{
+ var bin = Array();
+ var mask = (1 << chrsz) - 1;
+ for(var i = 0; i < str.length * chrsz; i += chrsz)
+ bin[i>>5] |= (str.charCodeAt(i / chrsz) & mask) << (i%32);
+ return bin;
+}
+
+/*
+ * Convert an array of little-endian words to a string
+ */
+function binl2str(bin)
+{
+ var str = "";
+ var mask = (1 << chrsz) - 1;
+ for(var i = 0; i < bin.length * 32; i += chrsz)
+ str += String.fromCharCode((bin[i>>5] >>> (i % 32)) & mask);
+ return str;
+}
+
+/*
+ * Convert an array of little-endian words to a hex string.
+ */
+function binl2hex(binarray)
+{
+ var hex_tab = hexcase ? "0123456789ABCDEF" : "0123456789abcdef";
+ var str = "";
+ for(var i = 0; i < binarray.length * 4; i++)
+ {
+ str += hex_tab.charAt((binarray[i>>2] >> ((i%4)*8+4)) & 0xF) +
+ hex_tab.charAt((binarray[i>>2] >> ((i%4)*8 )) & 0xF);
+ }
+ return str;
+}
+
+/*
+ * Convert an array of little-endian words to a base-64 string
+ */
+function binl2b64(binarray)
+{
+ var tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+ var str = "";
+ for(var i = 0; i < binarray.length * 4; i += 3)
+ {
+ var triplet = (((binarray[i >> 2] >> 8 * ( i %4)) & 0xFF) << 16)
+ | (((binarray[i+1 >> 2] >> 8 * ((i+1)%4)) & 0xFF) << 8 )
+ | ((binarray[i+2 >> 2] >> 8 * ((i+2)%4)) & 0xFF);
+ for(var j = 0; j < 4; j++)
+ {
+ if(i * 8 + j * 6 > binarray.length * 32) str += b64pad;
+ else str += tab.charAt((triplet >> 6*(3-j)) & 0x3F);
+ }
+ }
+ return str;
+}
--- /dev/null
+/* -----------------------------------------------------------------------
+ * 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;
+}
+
+
+
--- /dev/null
+/* -----------------------------------------------------------------------
+ * 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);
+}
+
+
--- /dev/null
+
+/**
+ * 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;
+}
+
+
+
+
+
--- /dev/null
+# 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
+
--- /dev/null
+/*
+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;
+}
+
+
+
+
--- /dev/null
+/*
+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
+
+
--- /dev/null
+#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;
+
+}
+
--- /dev/null
+# 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)
+
--- /dev/null
+#-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
+
--- /dev/null
+#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;
+
+}
+
+
+
+
--- /dev/null
+#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;
+}
+
+
--- /dev/null
+/* --- 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;
+}
+*/
--- /dev/null
+#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;
+}
+
+
--- /dev/null
+/* 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;
+}
+
--- /dev/null
+#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;
+}
+
+
+
+
+
+
--- /dev/null
+#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;
+}
+
--- /dev/null
+#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;
+}
+
+
+
--- /dev/null
+#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;
+}
--- /dev/null
+/*
+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);
+}
+
+
--- /dev/null
+/*
+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;
+}
--- /dev/null
+/*
+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 );
+}
--- /dev/null
+/*
+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;
+ }
+}
+
+
--- /dev/null
+/*
+ * 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);
+}
+
--- /dev/null
+/*
+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;
+}
+
+
+
+
--- /dev/null
+#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,"&");
+ else if (text[i] == '<')
+ buffer_add(b,"<");
+ else if (text[i] == '>')
+ buffer_add(b,">");
+ else
+ buffer_add_char(b,text[i]);
+ }
+ return buffer_release(b);
+}
+
+#endif
--- /dev/null
+/*
+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);
+}
+
+
--- /dev/null
+#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;
+}
--- /dev/null
+#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;
+}
+
--- /dev/null
+#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;
+}
+
--- /dev/null
+#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);
+}
--- /dev/null
+#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;
+}
+
+
--- /dev/null
+#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;
+}
+
+
+
+
--- /dev/null
+#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;
+}
+
+
--- /dev/null
+/*
+ * 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;
+}
--- /dev/null
+#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);
+
+}
+
--- /dev/null
+#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;
+}
--- /dev/null
+#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--;
+}
+
+
--- /dev/null
+#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;
+}
+
--- /dev/null
+#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;
+}
--- /dev/null
+#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 );
+}
+
--- /dev/null
+/*
+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;
+}
+
--- /dev/null
+#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;
+}
+
--- /dev/null
+Revision history for OpenSRF
+
+0.9 2006/07
+ First version, released on an unsuspecting world.
+
--- /dev/null
+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
--- /dev/null
+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;
--- /dev/null
+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.
+
--- /dev/null
+#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.
--- /dev/null
+#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
--- /dev/null
+#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
--- /dev/null
+#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;
--- /dev/null
+#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
--- /dev/null
+#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;
--- /dev/null
+#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;
--- /dev/null
+#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;
--- /dev/null
+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;
--- /dev/null
+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;
+
--- /dev/null
+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;
+
+
--- /dev/null
+package OpenSRF::App::Client;
+use base 'OpenSRF::Application';
+use OpenSRF::Utils::Logger qw/:level/;
+
+
+1;
--- /dev/null
+package OpenSRF::Application::Demo::Math;
+use base qw/OpenSRF::Application/;
+use OpenSRF::Application;
+use OpenSRF::Utils::Logger qw/:level/;
+use OpenSRF::DomainObject::oilsResponse;
+use OpenSRF::EX qw/:try/;
+use strict;
+use warnings;
+
+
+sub DESTROY{}
+
+our $log = 'OpenSRF::Utils::Logger';
+
+sub 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;
--- /dev/null
+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;
--- /dev/null
+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;
--- /dev/null
+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;
--- /dev/null
+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;
--- /dev/null
+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;
--- /dev/null
+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;
--- /dev/null
+package OpenSRF::EX;
+use Error qw(:try);
+use base qw( OpenSRF Error );
+use OpenSRF::Utils::Logger;
+
+my $log = "OpenSRF::Utils::Logger";
+$Error::Debug = 1;
+
+sub new {
+ my( $class, $message ) = @_;
+ $class = ref( $class ) || $class;
+ my $self = {};
+ $self->{'msg'} = ${$class . '::ex_msg_header'} .": $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;
--- /dev/null
+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;
+
--- /dev/null
+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;
--- /dev/null
+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;
--- /dev/null
+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;
--- /dev/null
+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;
--- /dev/null
+package OpenSRF::Transport::SlimJabber;
+use base qw/OpenSRF::Transport/;
+
+=head2 OpenSRF::Transport::SlimJabber
+
+Implements the Transport interface for providing the system with appropriate
+classes for handling transport layer messaging
+
+=cut
+
+
+sub get_listener { return "OpenSRF::Transport::SlimJabber::Inbound"; }
+
+sub get_peer_client { return "OpenSRF::Transport::SlimJabber::PeerConnection"; }
+
+sub get_msg_envelope { return "OpenSRF::Transport::SlimJabber::MessageWrapper"; }
+
+1;
--- /dev/null
+package OpenSRF::Transport::SlimJabber::Client;
+
+use strict;
+use warnings;
+
+use OpenSRF::EX;
+use 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;
+
+
--- /dev/null
+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;
+
--- /dev/null
+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;
--- /dev/null
+package OpenSRF::Transport::SlimJabber::PeerConnection;
+use strict;
+use base qw/OpenSRF::Transport::SlimJabber::Client/;
+use OpenSRF::Utils::Config;
+use OpenSRF::Utils::Logger qw(:level);
+use OpenSRF::EX qw/:try/;
+
+=head1 Description
+
+Represents a single connection to a remote peer. The
+Jabber values are loaded from the config file.
+
+Subclasses OpenSRF::Transport::SlimJabber::Client.
+
+=cut
+
+=head2 new()
+
+ new( $appname );
+
+ The $appname parameter tells this class how to find the correct
+ Jabber username, password, etc to connect to the server.
+
+=cut
+
+our %apps_hash;
+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;
+
--- /dev/null
+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/&/&/sog;
+ $body =~ s/</</sog;
+ $body =~ s/>/>/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;
--- /dev/null
+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;
+
+
+
+
+
--- /dev/null
+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;
+
--- /dev/null
+package OpenSRF::Utils;
+
+=head1 NAME
+
+OpenSRF::Utils
+
+=head1 DESCRIPTION
+
+This is a container package for methods that are useful to derived modules.
+It has no constructor, and is generally not useful by itself... but this
+is where most of the generic methods live.
+
+
+=head1 METHODS
+
+
+=cut
+
+use vars qw/@ISA $AUTOLOAD %EXPORT_TAGS @EXPORT_OK @EXPORT $VERSION/;
+push @ISA, 'Exporter';
+
+$VERSION = do { my @r=(q$Revision$=~/\d+/g); sprintf "%d."."%02d"x$#r,@r };
+
+use Time::Local;
+use Errno;
+use POSIX;
+use FileHandle;
+#use Cache::FileCache;
+#use Storable qw(dclone);
+use Digest::MD5 qw(md5 md5_hex md5_base64);
+use Exporter;
+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;
--- /dev/null
+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;
+
--- /dev/null
+package OpenSRF::Utils::Config::Section;
+
+no strict 'refs';
+
+use vars qw/@ISA $AUTOLOAD $VERSION/;
+push @ISA, qw/OpenSRF::Utils/;
+
+use OpenSRF::Utils (':common');
+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;
--- /dev/null
+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;
--- /dev/null
+package OpenSRF::Utils::LogServer;
+use strict; use warnings;
+use base qw(OpenSRF);
+use IO::Socket::INET;
+use FileHandle;
+use OpenSRF::Utils::Config;
+use Fcntl;
+use Time::HiRes qw(gettimeofday);
+use OpenSRF::Utils::Logger;
+
+=head2 Name
+
+OpenSRF::Utils::LogServer
+
+=cut
+
+=head2 Synopsis
+
+Networ Logger
+
+=cut
+
+=head2 Description
+
+
+=cut
+
+
+
+our $config;
+our $port;
+our $bufsize = 4096;
+our $proto;
+our @file_info;
+
+
+sub DESTROY {
+ for my $file (@file_info) {
+ if( $file->handle ) {
+ close( $file->handle );
+ }
+ }
+}
+
+
+sub serve {
+
+ $config = OpenSRF::Utils::Config->current;
+
+ unless ($config) { throw OpenSRF::EX::Config ("No suitable config found"); }
+
+ $port = $config->system->log_port;
+ $proto = $config->system->log_proto;
+
+
+ my $server = IO::Socket::INET->new(
+ LocalPort => $port,
+ Proto => $proto )
+ or die "Error creating server socket : $@\n";
+
+
+
+ while ( 1 ) {
+ my $client = <$server>;
+ process( $client );
+ }
+
+ close( $server );
+}
+
+sub process {
+ my $client = shift;
+ my @params = split(/\|/,$client);
+ my $log = shift @params;
+
+ if( (!$log) || (!@params) ) {
+ warn "Invalid logging params: $log\n";
+ return;
+ }
+
+ # Put |'s back in since they are stripped
+ # from the message by 'split'
+ my $message;
+ if( @params > 1 ) {
+ foreach my $param (@params) {
+ if( $param ne $params[0] ) {
+ $message .= "|";
+ }
+ $message .= $param;
+ }
+ }
+ else{ $message = "@params"; }
+
+ my @lines = split( "\n", $message );
+ my $time = format_time();
+
+ my $fh;
+
+ my ($f_obj) = grep { $_->name eq $log } @file_info;
+
+ unless( $f_obj and ($fh=$f_obj->handle) ) {
+ my $file = $config->logs->$log;
+
+ sysopen( $fh, $file, O_WRONLY|O_APPEND|O_CREAT )
+ or warn "Cannot sysopen $log: $!";
+ $fh->autoflush(1);
+
+ my $obj = new OpenSRF::Utils::NetLogFile( $log, $file, $fh );
+ push @file_info, $obj;
+ }
+
+ foreach my $line (@lines) {
+ print $fh "$time $line\n" || die "$!";
+ }
+
+}
+
+sub format_time {
+ my ($s, $ms) = gettimeofday();
+ my @time = localtime( $s );
+ $ms = substr( $ms, 0, 3 );
+ my $year = $time[5] + 1900;
+ my $mon = $time[4] + 1;
+ my $day = $time[3];
+ my $hour = $time[2];
+ my $min = $time[1];
+ my $sec = $time[0];
+ $mon = "0" . "$mon" if ( length($mon) == 1 );
+ $day = "0" . "$day" if ( length($day) == 1 );
+ $hour = "0" . "$hour" if ( length($hour) == 1 );
+ $min = "0" . "$min" if (length($min) == 1 );
+ $sec = "0" . "$sec" if (length($sec) == 1 );
+
+ my $proc = $$;
+ while( length( $proc ) < 5 ) { $proc = "0" . "$proc"; }
+ return "[$year-$mon-$day $hour:$min:$sec.$ms $proc]";
+}
+
+
+package OpenSRF::Utils::NetLogFile;
+
+sub new{ return bless( [ $_[1], $_[2], $_[3] ], $_[0] ); }
+
+sub name { return $_[0]->[0]; }
+sub file { return $_[0]->[1]; }
+sub handle { return $_[0]->[2]; }
+
+
+1;
--- /dev/null
+package OpenSRF::Utils::Logger;
+# 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;
--- /dev/null
+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;
--- /dev/null
+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;
--- /dev/null
+#!perl -T
+
+use Test::More tests => 1;
+
+BEGIN {
+ use_ok( 'OpenSRF' );
+}
+
+diag( "Testing OpenSRF $OpenSRF::VERSION, Perl $], $^X" );
--- /dev/null
+#!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' );
--- /dev/null
+#!perl -T
+
+use Test::More tests => 1;
+
+BEGIN {
+ use_ok( 'OpenSRF::AppSession' );
+}
--- /dev/null
+#!perl -T
+
+use Test::More tests => 3;
+
+use_ok( 'OpenSRF::DomainObject::oilsMessage' );
+use_ok( 'OpenSRF::DomainObject::oilsMethod' );
+use_ok( 'OpenSRF::DomainObject::oilsResponse' );
--- /dev/null
+#!perl -T
+
+use Test::More tests => 1;
+
+BEGIN {
+ use_ok( 'OpenSRF::EX' );
+}
--- /dev/null
+#!perl -T
+
+use Test::More tests => 1;
+
+BEGIN {
+ use_ok( 'OpenSRF::MultiSession' );
+}
--- /dev/null
+#!perl -T
+
+use Test::More tests => 1;
+
+BEGIN {
+ use_ok( 'OpenSRF::System' );
+}
--- /dev/null
+#!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' );
--- /dev/null
+#!perl -T
+
+use Test::More tests => 1;
+
+BEGIN {
+ use_ok( 'OpenSRF::UnixServer' );
+}
--- /dev/null
+#!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' );
--- /dev/null
+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();
--- /dev/null
+#!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();
--- /dev/null
+# 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
+
--- /dev/null
+/*
+ * 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;
+}
--- /dev/null
+/*
+ * 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);
+
--- /dev/null
+/*
+ * 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);
+}
--- /dev/null
+/*
+ * 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);
+
--- /dev/null
+# 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
+
--- /dev/null
+#!/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()
--- /dev/null
+# -----------------------------------------------------------------------
+# 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
+ })
+
+
--- /dev/null
+# -----------------------------------------------------------------------
+# 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())
+
+
--- /dev/null
+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
+
+
--- /dev/null
+# -----------------------------------------------------------------------
+# 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
+
--- /dev/null
+# -----------------------------------------------------------------------
+# 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'
+
+
--- /dev/null
+# -----------------------------------------------------------------------
+# 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
+
--- /dev/null
+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 += '¶m=%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))
+
+
+
+
+
--- /dev/null
+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)
+
+
--- /dev/null
+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
--- /dev/null
+# -----------------------------------------------------------------------
+# 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)
+
--- /dev/null
+# -----------------------------------------------------------------------
+# 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
+
+
+
--- /dev/null
+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
--- /dev/null
+# -----------------------------------------------------------------------
+# 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()
+
--- /dev/null
+# -----------------------------------------------------------------------
+# 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()
+
--- /dev/null
+# -----------------------------------------------------------------------
+# 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()
+
--- /dev/null
+# -----------------------------------------------------------------------
+# 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
+
+
--- /dev/null
+# -----------------------------------------------------------------------
+# 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)
+
+
--- /dev/null
+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
+
+
+
+
--- /dev/null
+#!/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']
+)
--- /dev/null
+#!/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()
+
--- /dev/null
+# 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
+
--- /dev/null
+#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;
+}
+
+
+
+
--- /dev/null
+#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
+
--- /dev/null
+#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;
+}
+
+
--- /dev/null
+# 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
--- /dev/null
+#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;
+}