--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>\r
+<chapter xml:id="opensrf" xmlns="http://docbook.org/ns/docbook" version="5.0" xml:lang="EN"\r
+ xmlns:xi="http://www.w3.org/2001/XInclude" xmlns:xlink="http://www.w3.org/1999/xlink">\r
+ <chapterinfo>\r
+ <title>OpenSRF</title>\r
+ </chapterinfo>\r
+ <abstract id="openSRF_abstract">\r
+ <simpara>One of the claimed advantages of\r
+ Evergreen over alternative integrated library systems is the underlying Open\r
+ Service Request Framework (OpenSRF, pronounced "open surf") architecture. This\r
+ article introduces OpenSRF, demonstrates how to build OpenSRF services through\r
+ simple code examples, and explains the technical foundations on which OpenSRF\r
+ is built. This chapter was taken from Dan Scott's <emphasis>Easing gently into OpenSRF</emphasis> article, June, 2010.</simpara>\r
+ </abstract>\r
+ <section id="_introducing_opensrf">\r
+ <title>Introducing OpenSRF</title>\r
+ <indexterm><primary>OpenSRF</primary></indexterm>\r
+ <simpara>OpenSRF is a message routing network that offers scalability and failover\r
+ support for individual services and entire servers with minimal development and\r
+ deployment overhead. You can use OpenSRF to build loosely-coupled applications\r
+ that can be deployed on a single server or on clusters of geographically\r
+ distributed servers using the same code and minimal configuration changes.\r
+ Although copyright statements on some of the OpenSRF code date back to Mike\r
+ Rylander’s original explorations in 2000, Evergreen was the first major\r
+ application to be developed with, and to take full advantage of, the OpenSRF\r
+ architecture starting in 2004. The first official release of OpenSRF was 0.1 in\r
+ February 2005 (<ulink url="http://evergreen-ils.org/blog/?p=21">http://evergreen-ils.org/blog/?p=21</ulink>), but OpenSRF’s development\r
+ continues a steady pace of enhancement and refinement, with the release of\r
+ 1.0.0 in October 2008 and the most recent release of 1.2.2 in February 2010.</simpara>\r
+ <simpara>OpenSRF is a distinct break from the architectural approach used by previous\r
+ library systems and has more in common with modern Web applications. The\r
+ traditional "scale-up" approach to serve more transactions is to purchase a\r
+ server with more CPUs and more RAM, possibly splitting the load between a Web\r
+ server, a database server, and a business logic server. Evergreen, however, is\r
+ built on the Open Service Request Framework (OpenSRF) architecture, which\r
+ firmly embraces the "scale-out" approach of spreading transaction load over\r
+ cheap commodity servers. The <ulink url="http://evergreen-ils.org/blog/?p=56">initial GPLS\r
+ PINES hardware cluster</ulink>, while certainly impressive, may have offered the\r
+ misleading impression that Evergreen requires a lot of hardware to run.\r
+ However, Evergreen and OpenSRF easily scale down to a single server; many\r
+ Evergreen libraries run their entire library system on a single server, and\r
+ most OpenSRF and Evergreen development occurs on a virtual machine running on a\r
+ single laptop or desktop image.</simpara>\r
+ <simpara>Another common concern is that the flexibility of OpenSRF’s distributed\r
+ architecture makes it complex to configure and to write new applications. This\r
+ article demonstrates that OpenSRF itself is an extremely simple architecture on\r
+ which one can easily build applications of many kinds – not just library\r
+ applications – and that you can use a number of different languages to call and\r
+ implement OpenSRF methods with a minimal learning curve. With an application\r
+ built on OpenSRF, when you identify a bottleneck in your application’s business\r
+ logic layer, you can adjust the number of the processes serving that particular\r
+ bottleneck on each of your servers; or if the problem is that your service is\r
+ resource-hungry, you could add an inexpensive server to your cluster and\r
+ dedicate it to running that resource-hungry service.</simpara>\r
+ <simplesect id="_programming_language_support">\r
+ <title>Programming language support</title>\r
+ <simpara>If you need to develop an entirely new OpenSRF service, you can choose from a\r
+ number of different languages in which to implement that service. OpenSRF\r
+ client language bindings have been written for C, Java, JavaScript, Perl, and\r
+ Python, and service language bindings have been written for C, Perl, and Python.\r
+ This article uses Perl examples as a lowest common denominator programming\r
+ language. Writing an OpenSRF binding for another language is a relatively small\r
+ task if that language offers libraries that support the core technologies on\r
+ which OpenSRF depends:</simpara>\r
+ <itemizedlist>\r
+ <listitem>\r
+ <simpara>\r
+ <ulink url="http://tools.ietf.org/html/rfc3920">Extensible Messaging and Presence\r
+ Protocol</ulink> (XMPP, sometimes referred to as Jabber) - provides the base messaging\r
+ infrastructure between OpenSRF clients and services\r
+ </simpara>\r
+ <indexterm><primary>XMPP</primary></indexterm>\r
+ </listitem>\r
+ <listitem>\r
+ <simpara>\r
+ <ulink url="http://json.org">JavaScript Object Notation</ulink> (JSON) - serializes the content\r
+ of each XMPP message in a standardized and concise format\r
+ </simpara>\r
+ </listitem>\r
+ <listitem>\r
+ <simpara>\r
+ <ulink url="http://memcached.org">memcached</ulink> - provides the caching service\r
+ </simpara>\r
+ <indexterm><primary>memcached</primary></indexterm>\r
+ </listitem>\r
+ <listitem>\r
+ <simpara>\r
+ <ulink url="http://tools.ietf.org/html/rfc5424">syslog</ulink> - the standard UNIX logging\r
+ service\r
+ </simpara>\r
+ <indexterm><primary>syslog</primary></indexterm>\r
+ </listitem>\r
+ </itemizedlist>\r
+ <simpara>Unfortunately, the\r
+ <ulink url="http://evergreen-ils.org/dokuwiki/doku.php?id=osrf-devel:primer">OpenSRF\r
+ reference documentation</ulink>, although augmented by the\r
+ <ulink url="http://evergreen-ils.org/dokuwiki/doku.php?id=osrf-devel:terms">OpenSRF\r
+ glossary</ulink>, blog posts like <ulink url="http://evergreen-ils.org/blog/?p=36">the description\r
+ of OpenSRF and Jabber</ulink>, and even this article, is not a sufficient substitute\r
+ for a complete specification on which one could implement a language binding.\r
+ The recommended option for would-be developers of another language binding is\r
+ to use the Python implementation as the cleanest basis for a port to another\r
+ language.</simpara>\r
+ <indexterm><primary>Python</primary></indexterm>\r
+ </simplesect>\r
+ </section>\r
+ <section id="writing_an_opensrf_service">\r
+ <title>Writing an OpenSRF Service</title>\r
+ <simpara>Imagine an application architecture in which 10 lines of Perl or Python, using\r
+ the data types native to each language, are enough to implement a method that\r
+ can then be deployed and invoked seamlessly across hundreds of servers. You\r
+ have just imagined developing with OpenSRF – it is truly that simple. Under the\r
+ covers, of course, the OpenSRF language bindings do an incredible amount of\r
+ work on behalf of the developer. An OpenSRF application consists of one or more\r
+ OpenSRF services that expose methods: for example, the <literal>opensrf.simple-text</literal>\r
+ <ulink url="http://svn.open-ils.org/trac/OpenSRF/browser/trunk/src/perl/lib/OpenSRF/Application/Demo/SimpleText.pm">demonstration\r
+ service</ulink> exposes the <literal>opensrf.simple-text.split()</literal> and\r
+ <literal>opensrf.simple-text.reverse()</literal> methods. Each method accepts zero or more\r
+ arguments and returns zero or one results. The data types supported by OpenSRF\r
+ arguments and results are typical core language data types: strings, numbers,\r
+ booleans, arrays, and hashes.</simpara>\r
+ <simpara>To implement a new OpenSRF service, perform the following steps:</simpara>\r
+ <orderedlist numeration="arabic">\r
+ <listitem>\r
+ <simpara>\r
+ Include the base OpenSRF support libraries\r
+ </simpara>\r
+ </listitem>\r
+ <listitem>\r
+ <simpara>\r
+ Write the code for each of your OpenSRF methods as separate procedures\r
+ </simpara>\r
+ </listitem>\r
+ <listitem>\r
+ <simpara>\r
+ Register each method\r
+ </simpara>\r
+ </listitem>\r
+ <listitem>\r
+ <simpara>\r
+ Add the service definition to the OpenSRF configuration files\r
+ </simpara>\r
+ </listitem>\r
+ </orderedlist>\r
+ <simpara>For example, the following code implements an OpenSRF service. The service\r
+ includes one method named <literal>opensrf.simple-text.reverse()</literal> that accepts one\r
+ string as input and returns the reversed version of that string:</simpara>\r
+<programlisting language="perl" linenumbering="unnumbered">\r
+#!/usr/bin/perl\r
+\r
+package OpenSRF::Application::Demo::SimpleText;\r
+\r
+use strict;\r
+\r
+use OpenSRF::Application;\r
+use parent qw/OpenSRF::Application/;\r
+\r
+sub text_reverse {\r
+ my ($self , $conn, $text) = @_;\r
+ my $reversed_text = scalar reverse($text);\r
+ return $reversed_text;\r
+}\r
+\r
+__PACKAGE__->register_method(\r
+ method => 'text_reverse',\r
+ api_name => 'opensrf.simple-text.reverse'\r
+);\r
+</programlisting>\r
+ <simpara>Ten lines of code, and we have a complete OpenSRF service that exposes a single\r
+ method and could be deployed quickly on a cluster of servers to meet your\r
+ application’s ravenous demand for reversed strings! If you’re unfamiliar with\r
+ Perl, the <literal>use OpenSRF::Application; use parent qw/OpenSRF::Application/;</literal>\r
+ lines tell this package to inherit methods and properties from the\r
+ <literal>OpenSRF::Application</literal> module. For example, the call to\r
+ <literal>__PACKAGE__->register_method()</literal> is defined in <literal>OpenSRF::Application</literal> but due to\r
+ inheritance is available in this package (named by the special Perl symbol\r
+ <literal>__PACKAGE__</literal> that contains the current package name). The <literal>register_method()</literal>\r
+ procedure is how we introduce a method to the rest of the OpenSRF world.</simpara>\r
+ <simplesect id="serviceRegistration">\r
+ <title>Registering a service with the OpenSRF configuration files</title>\r
+ <simpara>Two files control most of the configuration for OpenSRF:</simpara>\r
+ <itemizedlist>\r
+ <listitem>\r
+ <simpara>\r
+ <literal>opensrf.xml</literal> contains the configuration for the service itself, as well as\r
+ a list of which application servers in your OpenSRF cluster should start\r
+ the service.\r
+ </simpara>\r
+ </listitem>\r
+ <listitem>\r
+ <simpara>\r
+ <literal>opensrf_core.xml</literal> (often referred to as the "bootstrap configuration"\r
+ file) contains the OpenSRF networking information, including the XMPP server\r
+ connection credentials for the public and private routers. You only need to touch\r
+ this for a new service if the new service needs to be accessible via the\r
+ public router.\r
+ </simpara>\r
+ <indexterm><primary>configuration files</primary><secondary>opensrf_core.xml</secondary></indexterm>\r
+ </listitem>\r
+ </itemizedlist>\r
+ <simpara>Begin by defining the service itself in <literal>opensrf.xml</literal>. To register the\r
+ <literal>opensrf.simple-text</literal> service, add the following section to the <literal><apps></literal>\r
+ element (corresponding to the XPath <literal>/opensrf/default/apps/</literal>):</simpara>\r
+ <indexterm><primary>configuration files</primary><secondary>opensrf.xml</secondary></indexterm>\r
+<programlisting language="xml" linenumbering="unnumbered">\r
+<apps>\r
+ <opensrf.simple-text> <co id="CO1-1"/> \r
+ <keepalive>3</keepalive><co id="CO1-2"/> \r
+ <stateless>1</stateless><co id="CO1-3"/>\r
+ <language>perl</language><co id="CO1-4"/> \r
+ <implementation>OpenSRF::Application::Demo::SimpleText</implementation><co id="CO1-5"/> \r
+ <max_requests>100</max_requests><co id="CO1-6"/> \r
+ <unix_config>\r
+ <max_requests>1000</max_requests> <co id="CO1-7"/> \r
+ <unix_log>opensrf.simple-text_unix.log</unix_log> <co id="CO1-8"/> \r
+ <unix_sock>opensrf.simple-text_unix.sock</unix_sock><co id="CO1-9"/> \r
+ <unix_pid>opensrf.simple-text_unix.pid</unix_pid> <co id="CO1-10"/> \r
+ <min_children>5</min_children> <co id="CO1-11"/> \r
+ <max_children>15</max_children><co id="CO1-12"/> \r
+ <min_spare_children>2</min_spare_children><co id="CO1-13"/> \r
+ <max_spare_children>5</max_spare_children> <co id="CO1-14"/> \r
+ </unix_config>\r
+ </opensrf.simple-text>\r
+\r
+ <!-- other OpenSRF services registered here... -->\r
+</apps>\r
+</programlisting>\r
+ <calloutlist>\r
+ <callout arearefs="CO1-1">\r
+ <simpara>\r
+ The element name is the name that the OpenSRF control scripts use to refer\r
+ to the service.\r
+ </simpara>\r
+ </callout>\r
+ <callout arearefs="CO1-2">\r
+ <simpara>\r
+ The <literal><keepalive></literal> element specifies the interval (in seconds) between\r
+ checks to determine if the service is still running.\r
+ </simpara>\r
+ </callout>\r
+ <callout arearefs="CO1-3">\r
+ <simpara>\r
+ The <literal><stateless></literal> element specifies whether OpenSRF clients can call\r
+ methods from this service without first having to create a connection to a\r
+ specific service backend process for that service. If the value is <literal>1</literal>, then\r
+ the client can simply issue a request and the router will forward the request\r
+ to an available service and the result will be returned directly to the client.\r
+ </simpara>\r
+ </callout>\r
+ <callout arearefs="CO1-4">\r
+ <simpara>\r
+ The <literal><language></literal> element specifies the programming language in which the\r
+ service is implemented.\r
+ </simpara>\r
+ </callout>\r
+ <callout arearefs="CO1-5">\r
+ <simpara>\r
+ The <literal><implementation></literal> element pecifies the name of the library or module\r
+ in which the service is implemented.\r
+ </simpara>\r
+ </callout>\r
+ <callout arearefs="CO1-6">\r
+ <simpara>\r
+ (C implementations only): The <literal><max_requests></literal> element, as a direct child\r
+ of the service element name, specifies the maximum number of requests a process\r
+ serves before it is killed and replaced by a new process.\r
+ </simpara>\r
+ </callout>\r
+ <callout arearefs="CO1-7">\r
+ <simpara>\r
+ (Perl implementations only): The <literal><max_requests></literal> element, as a direct\r
+ child of the <literal><unix_config></literal> element, specifies the maximum number of requests\r
+ a process serves before it is killed and replaced by a new process.\r
+ </simpara>\r
+ </callout>\r
+ <callout arearefs="CO1-8">\r
+ <simpara>\r
+ The <literal><unix_log></literal> element specifies the name of the log file for\r
+ language-specific log messages such as syntax warnings.\r
+ </simpara>\r
+ </callout>\r
+ <callout arearefs="CO1-9">\r
+ <simpara>\r
+ The <literal><unix_sock></literal> element specifies the name of the UNIX socket used for\r
+ inter-process communications.\r
+ </simpara>\r
+ </callout>\r
+ <callout arearefs="CO1-10">\r
+ <simpara>\r
+ The <literal><unix_pid></literal> element specifies the name of the PID file for the\r
+ master process for the service.\r
+ </simpara>\r
+ </callout>\r
+ <callout arearefs="CO1-11">\r
+ <simpara>\r
+ The <literal><min_children></literal> element specifies the minimum number of child\r
+ processes that should be running at any given time.\r
+ </simpara>\r
+ </callout>\r
+ <callout arearefs="CO1-12">\r
+ <simpara>\r
+ The <literal><max_children></literal> element specifies the maximum number of child\r
+ processes that should be running at any given time.\r
+ </simpara>\r
+ </callout>\r
+ <callout arearefs="CO1-13">\r
+ <simpara>\r
+ The <literal><min_spare_children></literal> element specifies the minimum number of idle\r
+ child processes that should be available to handle incoming requests. If there\r
+ are fewer than this number of spare child processes, new processes will be\r
+ spawned.\r
+ </simpara>\r
+ </callout>\r
+ <callout arearefs="CO1-14">\r
+ <simpara>\r
+ The`<max_spare_children>` element specifies the maximum number of idle\r
+ child processes that should be available to handle incoming requests. If there\r
+ are more than this number of spare child processes, the extra processes will be\r
+ killed.\r
+ </simpara>\r
+ </callout>\r
+ </calloutlist>\r
+ <simpara>To make the service accessible via the public router, you must also\r
+ edit the <literal>opensrf_core.xml</literal> configuration file to add the service to the list\r
+ of publicly accessible services:</simpara>\r
+ <formalpara><title>Making a service publicly accessible in <literal>opensrf_core.xml</literal></title><para>\r
+<programlisting language="xml" linenumbering="unnumbered">\r
+<router><co id="CO2-1"/> \r
+ <!-- This is the public router. On this router, we only register applications\r
+ which should be accessible to everyone on the opensrf network -->\r
+ <name>router</name>\r
+ <domain>public.localhost</domain><co id="CO2-2"/>\r
+ <services>\r
+ <service>opensrf.math</service>\r
+ <service>opensrf.simple-text</service> <co id="CO2-3"/> \r
+ </services>\r
+</router>\r
+</programlisting>\r
+ </para></formalpara>\r
+ <calloutlist>\r
+ <callout arearefs="CO2-1">\r
+ <simpara>\r
+ This section of the <literal>opensrf_core.xml</literal> file is located at XPath\r
+ <literal>/config/opensrf/routers/</literal>.\r
+ </simpara>\r
+ </callout>\r
+ <callout arearefs="CO2-2">\r
+ <simpara>\r
+ <literal>public.localhost</literal> is the canonical public router domain in the OpenSRF\r
+ installation instructions.\r
+ </simpara>\r
+ </callout>\r
+ <callout arearefs="CO2-3">\r
+ <simpara>\r
+ Each <literal><service></literal> element contained in the <literal><services></literal> element\r
+ offers their services via the public router as well as the private router.\r
+ </simpara>\r
+ </callout>\r
+ </calloutlist>\r
+ <simpara>Once you have defined the new service, you must restart the OpenSRF Router\r
+ to retrieve the new configuration and start or restart the service itself.</simpara>\r
+ </simplesect>\r
+ <simplesect id="_calling_an_opensrf_method">\r
+ <title>Calling an OpenSRF method</title>\r
+ <indexterm><primary>srfsh</primary></indexterm>\r
+ <simpara>OpenSRF clients in any supported language can invoke OpenSRF services in any\r
+ supported language. So let’s see a few examples of how we can call our fancy\r
+ new <literal>opensrf.simple-text.reverse()</literal> method:</simpara>\r
+ <simplesect id="_calling_opensrf_methods_from_the_srfsh_client">\r
+ <title>Calling OpenSRF methods from the srfsh client</title>\r
+ <simpara><literal>srfsh</literal> is a command-line tool installed with OpenSRF that you can use to call\r
+ OpenSRF methods. To call an OpenSRF method, issue the <literal>request</literal> command and\r
+ pass the OpenSRF service and method name as the first two arguments; then pass\r
+ one or more JSON objects delimited by commas as the arguments to the method\r
+ being invoked.</simpara>\r
+ <simpara>The following example calls the <literal>opensrf.simple-text.reverse</literal> method of the\r
+ <literal>opensrf.simple-text</literal> OpenSRF service, passing the string <literal>"foobar"</literal> as the\r
+ only method argument:</simpara>\r
+<programlisting language="sh" linenumbering="unnumbered">\r
+$ srfsh\r
+srfsh # request opensrf.simple-text opensrf.simple-text.reverse "foobar"\r
+\r
+Received Data: "raboof"\r
+\r
+=------------------------------------\r
+Request Completed Successfully\r
+Request Time in seconds: 0.016718\r
+=------------------------------------\r
+</programlisting>\r
+ </simplesect>\r
+ <simplesect id="opensrfIntrospection">\r
+ <title>Getting documentation for OpenSRF methods from the srfsh client</title>\r
+ <simpara>The <literal>srfsh</literal> client also gives you command-line access to retrieving metadata\r
+ about OpenSRF services and methods. For a given OpenSRF method, for example,\r
+ you can retrieve information such as the minimum number of required arguments,\r
+ the data type and a description of each argument, the package or library in\r
+ which the method is implemented, and a description of the method. To retrieve\r
+ the documentation for an opensrf method from <literal>srfsh</literal>, issue the <literal>introspect</literal>\r
+ command, followed by the name of the OpenSRF service and (optionally) the\r
+ name of the OpenSRF method. If you do not pass a method name to the <literal>introspect</literal>\r
+ command, <literal>srfsh</literal> lists all of the methods offered by the service. If you pass\r
+ a partial method name, <literal>srfsh</literal> lists all of the methods that match that portion\r
+ of the method name.</simpara>\r
+ <note><simpara>The quality and availability of the descriptive information for each\r
+ method depends on the developer to register the method with complete and\r
+ accurate information. The quality varies across the set of OpenSRF and\r
+ Evergreen APIs, although some effort is being put towards improving the\r
+ state of the internal documentation.</simpara></note>\r
+<programlisting language="sh" linenumbering="unnumbered">\r
+srfsh# introspect opensrf.simple-text "opensrf.simple-text.reverse"\r
+--> opensrf.simple-text\r
+\r
+Received Data: {\r
+ "__c":"opensrf.simple-text",\r
+ "__p":{\r
+ "api_level":1,\r
+ "stream":0, <co id="CO3-1"/>\r
+ "object_hint":"OpenSRF_Application_Demo_SimpleText",\r
+ "remote":0,\r
+ "package":"OpenSRF::Application::Demo::SimpleText", <co id="CO3-2"/>\r
+ "api_name":"opensrf.simple-text.reverse",<co id="CO3-3"/>\r
+ "server_class":"opensrf.simple-text",\r
+ "signature":{ <co id="CO3-4"/>\r
+ "params":[ <co id="CO3-5"/>\r
+ {\r
+ "desc":"The string to reverse",\r
+ "name":"text",\r
+ "type":"string"\r
+ }\r
+ ],\r
+ "desc":"Returns the input string in reverse order\n", <co id="CO3-6"/>\r
+ "return":{ <co id="CO3-7"/>\r
+ "desc":"Returns the input string in reverse order",\r
+ "type":"string"\r
+ }\r
+ },\r
+ "method":"text_reverse", <co id="CO3-8"/>\r
+ "argc":1 <co id="CO3-9"/>\r
+ }\r
+}\r
+</programlisting>\r
+ <calloutlist>\r
+ <callout arearefs="CO3-1">\r
+ <simpara>\r
+ <literal>stream</literal> denotes whether the method supports streaming responses or not.\r
+ </simpara>\r
+ </callout>\r
+ <callout arearefs="CO3-2">\r
+ <simpara>\r
+ <literal>package</literal> identifies which package or library implements the method.\r
+ </simpara>\r
+ </callout>\r
+ <callout arearefs="CO3-3">\r
+ <simpara>\r
+ <literal>api_name</literal> identifies the name of the OpenSRF method.\r
+ </simpara>\r
+ </callout>\r
+ <callout arearefs="CO3-4">\r
+ <simpara>\r
+ <literal>signature</literal> is a hash that describes the parameters for the method.\r
+ </simpara>\r
+ </callout>\r
+ <callout arearefs="CO3-5">\r
+ <simpara>\r
+ <literal>params</literal> is an array of hashes describing each parameter in the method;\r
+ each parameter has a description (<literal>desc</literal>), name (<literal>name</literal>), and type (<literal>type</literal>).\r
+ </simpara>\r
+ </callout>\r
+ <callout arearefs="CO3-6">\r
+ <simpara>\r
+ <literal>desc</literal> is a string that describes the method itself.\r
+ </simpara>\r
+ </callout>\r
+ <callout arearefs="CO3-7">\r
+ <simpara>\r
+ <literal>return</literal> is a hash that describes the return value for the method; it\r
+ contains a description of the return value (<literal>desc</literal>) and the type of the\r
+ returned value (<literal>type</literal>).\r
+ </simpara>\r
+ </callout>\r
+ <callout arearefs="CO3-8">\r
+ <simpara>\r
+ <literal>method</literal> identifies the name of the function or method in the source\r
+ implementation.\r
+ </simpara>\r
+ </callout>\r
+ <callout arearefs="CO3-9">\r
+ <simpara>\r
+ <literal>argc</literal> is an integer describing the minimum number of arguments that\r
+ must be passed to this method.\r
+ </simpara>\r
+ </callout>\r
+ </calloutlist>\r
+ </simplesect>\r
+ <simplesect id="_calling_opensrf_methods_from_perl_applications">\r
+ <title>Calling OpenSRF methods from Perl applications</title>\r
+ <simpara>To call an OpenSRF method from Perl, you must connect to the OpenSRF service,\r
+ issue the request to the method, and then retrieve the results.</simpara>\r
+<programlisting language="perl" linenumbering="unnumbered">\r
+#/usr/bin/perl\r
+use strict;\r
+use OpenSRF::AppSession;\r
+use OpenSRF::System;\r
+\r
+OpenSRF::System->bootstrap_client(config_file => '/openils/conf/opensrf_core.xml');<co id="CO4-1"/>\r
+\r
+my $session = OpenSRF::AppSession->create("opensrf.simple-text");<co id="CO4-2"/>\r
+\r
+print "substring: Accepts a string and a number as input, returns a string\n";\r
+my $result = $session->request("opensrf.simple-text.substring", "foobar", 3);<co id="CO4-3"/>\r
+my $request = $result->gather(); <co id="CO4-4"/>\r
+print "Substring: $request\n\n";\r
+\r
+print "split: Accepts two strings as input, returns an array of strings\n";\r
+$request = $session->request("opensrf.simple-text.split", "This is a test", " ");<co id="CO4-5"/>\r
+my $output = "Split: [";\r
+my $element;\r
+while ($element = $request->recv()) { <co id="CO4-6"/>\r
+ $output .= $element->content . ", "; <co id="CO4-7"/>\r
+}\r
+$output =~ s/, $/]/;\r
+print $output . "\n\n";\r
+\r
+print "statistics: Accepts an array of strings as input, returns a hash\n";\r
+my @many_strings = [\r
+ "First I think I'll have breakfast",\r
+ "Then I think that lunch would be nice",\r
+ "And then seventy desserts to finish off the day"\r
+];\r
+\r
+$result = $session->request("opensrf.simple-text.statistics", @many_strings); <co id="CO4-8"/>\r
+$request = $result->gather(); <co id="CO4-9"/>\r
+print "Length: " . $result->{'length'} . "\n";\r
+print "Word count: " . $result->{'word_count'} . "\n";\r
+\r
+$session->disconnect(); <co id="CO4-10"/>\r
+</programlisting>\r
+ <calloutlist>\r
+ <callout arearefs="CO4-1">\r
+ <simpara>\r
+ The <literal>OpenSRF::System->bootstrap_client()</literal> method reads the OpenSRF\r
+ configuration information from the indicated file and creates an XMPP client\r
+ connection based on that information.\r
+ </simpara>\r
+ </callout>\r
+ <callout arearefs="CO4-2">\r
+ <simpara>\r
+ The <literal>OpenSRF::AppSession->create()</literal> method accepts one argument - the name\r
+ of the OpenSRF service to which you want to want to make one or more requests -\r
+ and returns an object prepared to use the client connection to make those\r
+ requests.\r
+ </simpara>\r
+ </callout>\r
+ <callout arearefs="CO4-3">\r
+ <simpara>\r
+ The <literal>OpenSRF::AppSession->request()</literal> method accepts a minimum of one\r
+ argument - the name of the OpenSRF method to which you want to make a request -\r
+ followed by zero or more arguments to pass to the OpenSRF method as input\r
+ values. This example passes a string and an integer to the\r
+ <literal>opensrf.simple-text.substring</literal> method defined by the <literal>opensrf.simple-text</literal>\r
+ OpenSRF service.\r
+ </simpara>\r
+ </callout>\r
+ <callout arearefs="CO4-4">\r
+ <simpara>\r
+ The <literal>gather()</literal> method, called on the result object returned by the\r
+ <literal>request()</literal> method, iterates over all of the possible results from the result\r
+ object and returns a single variable.\r
+ </simpara>\r
+ </callout>\r
+ <callout arearefs="CO4-5">\r
+ <simpara>\r
+ This <literal>request()</literal> call passes two strings to the <literal>opensrf.simple-text.split</literal>\r
+ method defined by the <literal>opensrf.simple-text</literal> OpenSRF service and returns (via\r
+ <literal>gather()</literal>) a reference to an array of results.\r
+ </simpara>\r
+ </callout>\r
+ <callout arearefs="CO4-6">\r
+ <simpara>\r
+ The <literal>opensrf.simple-text.split()</literal> method is a streaming method that\r
+ returns an array of results with one element per <literal>recv()</literal> call on the\r
+ result object. We could use the <literal>gather()</literal> method to retrieve all of the\r
+ results in a single array reference, but instead we simply iterate over\r
+ the result variable until there are no more results to retrieve.\r
+ </simpara>\r
+ </callout>\r
+ <callout arearefs="CO4-7">\r
+ <simpara>\r
+ While the <literal>gather()</literal> convenience method returns only the content of the\r
+ complete set of results for a given request, the <literal>recv()</literal> method returns an\r
+ OpenSRF result object with <literal>status</literal>, <literal>statusCode</literal>, and <literal>content</literal> fields as\r
+ we saw in <link linkend="OpenSRFOverHTTP">the HTTP results example</link>.\r
+ </simpara>\r
+ </callout>\r
+ <callout arearefs="CO4-8">\r
+ <simpara>\r
+ This <literal>request()</literal> call passes an array to the\r
+ <literal>opensrf.simple-text.statistics</literal> method defined by the <literal>opensrf.simple-text</literal>\r
+ OpenSRF service.\r
+ </simpara>\r
+ </callout>\r
+ <callout arearefs="CO4-9">\r
+ <simpara>\r
+ The result object returns a hash reference via <literal>gather()</literal>. The hash\r
+ contains the <literal>length</literal> and <literal>word_count</literal> keys we defined in the method.\r
+ </simpara>\r
+ </callout>\r
+ <callout arearefs="CO4-10">\r
+ <simpara>\r
+ The <literal>OpenSRF::AppSession->disconnect()</literal> method closes the XMPP client\r
+ connection and cleans up resources associated with the session.\r
+ </simpara>\r
+ </callout>\r
+ </calloutlist>\r
+ </simplesect>\r
+ </simplesect>\r
+ <simplesect id="_accepting_and_returning_more_interesting_data_types">\r
+ <title>Accepting and returning more interesting data types</title>\r
+ <simpara>Of course, the example of accepting a single string and returning a single\r
+ string is not very interesting. In real life, our applications tend to pass\r
+ around multiple arguments, including arrays and hashes. Fortunately, OpenSRF\r
+ makes that easy to deal with; in Perl, for example, returning a reference to\r
+ the data type does the right thing. In the following example of a method that\r
+ returns a list, we accept two arguments of type string: the string to be split,\r
+ and the delimiter that should be used to split the string.</simpara>\r
+ <formalpara><title>Basic text splitting method</title><para>\r
+<programlisting language="perl" linenumbering="unnumbered">\r
+sub text_split {\r
+ my $self = shift;\r
+ my $conn = shift;\r
+ my $text = shift;\r
+ my $delimiter = shift || ' ';\r
+\r
+ my @split_text = split $delimiter, $text;\r
+ return \@split_text;\r
+}\r
+\r
+__PACKAGE__->register_method(\r
+ method => 'text_split',\r
+ api_name => 'opensrf.simple-text.split'\r
+);\r
+</programlisting>\r
+ </para></formalpara>\r
+ <simpara>We simply return a reference to the list, and OpenSRF does the rest of the work\r
+ for us to convert the data into the language-independent format that is then\r
+ returned to the caller. As a caller of a given method, you must rely on the\r
+ documentation used to register to determine the data structures - if the developer has\r
+ added the appropriate documentation.</simpara>\r
+ </simplesect>\r
+ <simplesect id="_accepting_and_returning_evergreen_objects">\r
+ <title>Accepting and returning Evergreen objects</title>\r
+ <simpara>OpenSRF is agnostic about objects; its role is to pass JSON back and forth\r
+ between OpenSRF clients and services, and it allows the specific clients and\r
+ services to define their own semantics for the JSON structures. On top of that\r
+ infrastructure, Evergreen offers the fieldmapper: an object-relational mapper\r
+ that provides a complete definition of all objects, their properties, their\r
+ relationships to other objects, the permissions required to create, read,\r
+ update, or delete objects of that type, and the database table or view on which\r
+ they are based.</simpara>\r
+ <indexterm><primary>Fieldmapper</primary></indexterm>\r
+ <simpara>The Evergreen fieldmapper offers a great deal of convenience for working with\r
+ complex system objects beyond the basic mapping of classes to database\r
+ schemas. Although the result is passed over the wire as a JSON object\r
+ containing the indicated fields, fieldmapper-aware clients then turn those\r
+ JSON objects into native objects with setter / getter methods for each field.</simpara>\r
+ <simpara>All of this metadata about Evergreen objects is defined in the\r
+ fieldmapper configuration file (<literal>/openils/conf/fm_IDL.xml</literal>), and access to\r
+ these classes is provided by the <literal>open-ils.cstore</literal>, <literal>open-ils.pcrud</literal>, and\r
+ <literal>open-ils.reporter-store</literal> OpenSRF services which parse the fieldmapper\r
+ configuration file and dynamically register OpenSRF methods for creating,\r
+ reading, updating, and deleting all of the defined classes.</simpara>\r
+ <formalpara><title>Example fieldmapper class definition for "Open User Summary"</title><para>\r
+<programlisting language="xml" linenumbering="unnumbered">\r
+<class id="mous" controller="open-ils.cstore open-ils.pcrud"\r
+ oils_obj:fieldmapper="money::open_user_summary"\r
+ oils_persist:tablename="money.open_usr_summary"\r
+ reporter:label="Open User Summary"> <co id="CO5-1"/>\r
+ <fields oils_persist:primary="usr" oils_persist:sequence=""> <co id="CO5-2"/> \r
+ <field name="balance_owed" reporter:datatype="money" /> <co id="CO5-3"/> \r
+ <field name="total_owed" reporter:datatype="money" />\r
+ <field name="total_paid" reporter:datatype="money" />\r
+ <field name="usr" reporter:datatype="link"/>\r
+ </fields>\r
+ <links>\r
+ <link field="usr" reltype="has_a" key="id" map="" class="au"/><co id="CO5-4"/> \r
+ </links>\r
+ <permacrud xmlns="http://open-ils.org/spec/opensrf/IDL/permacrud/v1"><co id="CO5-5"/> \r
+ <actions>\r
+ <retrieve permission="VIEW_USER"><co id="CO5-6"/> \r
+ <context link="usr" field="home_ou"/><co id="CO5-7"/>\r
+ </retrieve>\r
+ </actions>\r
+ </permacrud>\r
+</class>\r
+</programlisting>\r
+ </para></formalpara>\r
+ <calloutlist>\r
+ <callout arearefs="CO5-1">\r
+ <simpara>\r
+ The <literal><class></literal> element defines the class:\r
+ </simpara>\r
+ <itemizedlist>\r
+ <listitem>\r
+ <simpara>\r
+ The <literal>id</literal> attribute defines the <emphasis>class hint</emphasis> that identifies the class both\r
+ elsewhere in the fieldmapper configuration file, such as in the value of the\r
+ <literal>field</literal> attribute of the <literal><link></literal> element, and in the JSON object itself when\r
+ it is instantiated. For example, an "Open User Summary" JSON object would have\r
+ the top level property of <literal>"__c":"mous"</literal>.\r
+ </simpara>\r
+ </listitem>\r
+ <listitem>\r
+ <simpara>\r
+ The <literal>controller</literal> attribute identifies the services that have direct access\r
+ to this class. If <literal>open-ils.pcrud</literal> is not listed, for example, then there is\r
+ no means to directly access members of this class through a public service.\r
+ </simpara>\r
+ </listitem>\r
+ <listitem>\r
+ <simpara>\r
+ The <literal>oils_obj:fieldmapper</literal> attribute defines the name of the Perl\r
+ fieldmapper class that will be dynamically generated to provide setter and\r
+ getter methods for instances of the class.\r
+ </simpara>\r
+ </listitem>\r
+ <listitem>\r
+ <simpara>\r
+ The <literal>oils_persist:tablename</literal> attribute identifies the schema name and table\r
+ name of the database table that stores the data that represents the instances\r
+ of this class. In this case, the schema is <literal>money</literal> and the table is\r
+ <literal>open_usr_summary</literal>.\r
+ </simpara>\r
+ </listitem>\r
+ <listitem>\r
+ <simpara>\r
+ The <literal>reporter:label</literal> attribute defines a human-readable name for the class\r
+ used in the reporting interface to identify the class. These names are defined\r
+ in English in the fieldmapper configuration file; however, they are extracted\r
+ so that they can be translated and served in the user’s language of choice.\r
+ </simpara>\r
+ </listitem>\r
+ </itemizedlist>\r
+ </callout>\r
+ <callout arearefs="CO5-2">\r
+ <simpara>\r
+ The <literal><fields></literal> element lists all of the fields that belong to the object.\r
+ </simpara>\r
+ <itemizedlist>\r
+ <listitem>\r
+ <simpara>\r
+ The <literal>oils_persist:primary</literal> attribute identifies the field that acts as the\r
+ primary key for the object; in this case, the field with the name <literal>usr</literal>.\r
+ </simpara>\r
+ </listitem>\r
+ <listitem>\r
+ <simpara>\r
+ The <literal>oils_persist:sequence</literal> attribute identifies the sequence object\r
+ (if any) in this database provides values for new instances of this class. In\r
+ this case, the primary key is defined by a field that is linked to a different\r
+ table, so no sequence is used to populate these instances.\r
+ </simpara>\r
+ </listitem>\r
+ </itemizedlist>\r
+ </callout>\r
+ <callout arearefs="CO5-3">\r
+ <simpara>\r
+ Each <literal><field></literal> element defines a single field with the following attributes:\r
+ </simpara>\r
+ <itemizedlist>\r
+ <listitem>\r
+ <simpara>\r
+ The <literal>name</literal> attribute identifies the column name of the field in the\r
+ underlying database table as well as providing a name for the setter / getter\r
+ method that can be invoked in the JSON or native version of the object.\r
+ </simpara>\r
+ </listitem>\r
+ <listitem>\r
+ <simpara>\r
+ The <literal>reporter:datatype</literal> attribute defines how the reporter should treat\r
+ the contents of the field for the purposes of querying and display.\r
+ </simpara>\r
+ </listitem>\r
+ <listitem>\r
+ <simpara>\r
+ The <literal>reporter:label</literal> attribute can be used to provide a human-readable name\r
+ for each field; without it, the reporter falls back to the value of the <literal>name</literal>\r
+ attribute.\r
+ </simpara>\r
+ </listitem>\r
+ </itemizedlist>\r
+ </callout>\r
+ <callout arearefs="CO5-4">\r
+ <simpara>\r
+ The <literal><links></literal> element contains a set of zero or more <literal><link></literal> elements,\r
+ each of which defines a relationship between the class being described and\r
+ another class.\r
+ </simpara>\r
+ <itemizedlist>\r
+ <listitem>\r
+ <simpara>\r
+ The <literal>field</literal> attribute identifies the field named in this class that links\r
+ to the external class.\r
+ </simpara>\r
+ </listitem>\r
+ <listitem>\r
+ <simpara>\r
+ The <literal>reltype</literal> attribute identifies the kind of relationship between the\r
+ classes; in the case of <literal>has_a</literal>, each value in the <literal>usr</literal> field is guaranteed\r
+ to have a corresponding value in the external class.\r
+ </simpara>\r
+ </listitem>\r
+ <listitem>\r
+ <simpara>\r
+ The <literal>key</literal> attribute identifies the name of the field in the external\r
+ class to which this field links.\r
+ </simpara>\r
+ </listitem>\r
+ <listitem>\r
+ <simpara>\r
+ The rarely-used <literal>map</literal> attribute identifies a second class to which\r
+ the external class links; it enables this field to define a direct\r
+ relationship to an external class with one degree of separation, to\r
+ avoid having to retrieve all of the linked members of an intermediate\r
+ class just to retrieve the instances from the actual desired target class.\r
+ </simpara>\r
+ </listitem>\r
+ <listitem>\r
+ <simpara>\r
+ The <literal>class</literal> attribute identifies the external class to which this field\r
+ links.\r
+ </simpara>\r
+ </listitem>\r
+ </itemizedlist>\r
+ </callout>\r
+ <callout arearefs="CO5-5">\r
+ <simpara>\r
+ The <literal><permacrud></literal> element defines the permissions that must have been\r
+ granted to a user to operate on instances of this class.\r
+ </simpara>\r
+ </callout>\r
+ <callout arearefs="CO5-6">\r
+ <simpara>\r
+ The <literal><retrieve></literal> element is one of four possible children of the\r
+ <literal><actions></literal> element that define the permissions required for each action:\r
+ create, retrieve, update, and delete.\r
+ </simpara>\r
+ <itemizedlist>\r
+ <listitem>\r
+ <simpara>\r
+ The <literal>permission</literal> attribute identifies the name of the permission that must\r
+ have been granted to the user to perform the action.\r
+ </simpara>\r
+ </listitem>\r
+ <listitem>\r
+ <simpara>\r
+ The <literal>contextfield</literal> attribute, if it exists, defines the field in this class\r
+ that identifies the library within the system for which the user must have\r
+ prvileges to work. If a user has been granted a given permission, but has not been\r
+ granted privileges to work at a given library, they can not perform the action\r
+ at that library.\r
+ </simpara>\r
+ </listitem>\r
+ </itemizedlist>\r
+ </callout>\r
+ <callout arearefs="CO5-7">\r
+ <simpara>\r
+ The rarely-used <literal><context></literal> element identifies a linked field (<literal>link</literal>\r
+ attribute) in this class which links to an external class that holds the field\r
+ (<literal>field</literal> attribute) that identifies the library within the system for which the\r
+ user must have privileges to work.\r
+ </simpara>\r
+ </callout>\r
+ </calloutlist>\r
+ <simpara>When you retrieve an instance of a class, you can ask for the result to\r
+ <emphasis>flesh</emphasis> some or all of the linked fields of that class, so that the linked\r
+ instances are returned embedded directly in your requested instance. In that\r
+ same request you can ask for the fleshed instances to in turn have their linked\r
+ fields fleshed. By bundling all of this into a single request and result\r
+ sequence, you can avoid the network overhead of requiring the client to request\r
+ the base object, then request each linked object in turn.</simpara>\r
+ <simpara>You can also iterate over a collection of instances and set the automatically\r
+ generated <literal>isdeleted</literal>, <literal>isupdated</literal>, or <literal>isnew</literal> properties to indicate that\r
+ the given instance has been deleted, updated, or created respectively.\r
+ Evergreen can then act in batch mode over the collection to perform the\r
+ requested actions on any of the instances that have been flagged for action.</simpara>\r
+ </simplesect>\r
+ <simplesect id="_returning_streaming_results">\r
+ <title>Returning streaming results</title>\r
+ <simpara>In the previous implementation of the <literal>opensrf.simple-text.split</literal> method, we\r
+ returned a reference to the complete array of results. For small values being\r
+ delivered over the network, this is perfectly acceptable, but for large sets of\r
+ values this can pose a number of problems for the requesting client. Consider a\r
+ service that returns a set of bibliographic records in response to a query like\r
+ "all records edited in the past month"; if the underlying database is\r
+ relatively active, that could result in thousands of records being returned as\r
+ a single network request. The client would be forced to block until all of the\r
+ results are returned, likely resulting in a significant delay, and depending on\r
+ the implementation, correspondingly large amounts of memory might be consumed\r
+ as all of the results are read from the network in a single block.</simpara>\r
+ <simpara>OpenSRF offers a solution to this problem. If the method returns results that\r
+ can be divided into separate meaningful units, you can register the OpenSRF\r
+ method as a streaming method and enable the client to loop over the results one\r
+ unit at a time until the method returns no further results. In addition to\r
+ registering the method with the provided name, OpenSRF also registers an additional\r
+ method with <literal>.atomic</literal> appended to the method name. The <literal>.atomic</literal> variant gathers\r
+ all of the results into a single block to return to the client, giving the caller\r
+ the ability to choose either streaming or atomic results from a single method\r
+ definition.</simpara>\r
+ <simpara>In the following example, the text splitting method has been reimplemented to\r
+ support streaming; very few changes are required:</simpara>\r
+ <formalpara><title>Text splitting method - streaming mode</title><para>\r
+<programlisting language="perl" linenumbering="unnumbered">\r
+sub text_split {\r
+ my $self = shift;\r
+ my $conn = shift;\r
+ my $text = shift;\r
+ my $delimiter = shift || ' ';\r
+\r
+ my @split_text = split $delimiter, $text;\r
+ foreach my $string (@split_text) { <co id="CO6-1"/>\r
+ $conn->respond($string);\r
+ }\r
+ return undef;\r
+}\r
+\r
+__PACKAGE__->register_method(\r
+ method => 'text_split',\r
+ api_name => 'opensrf.simple-text.split',\r
+ stream => 1<co id="CO6-2"/>\r
+);\r
+</programlisting>\r
+ </para></formalpara>\r
+ <calloutlist>\r
+ <callout arearefs="CO6-1">\r
+ <simpara>\r
+ Rather than returning a reference to the array, a streaming method loops\r
+ over the contents of the array and invokes the <literal>respond()</literal> method of the\r
+ connection object on each element of the array.\r
+ </simpara>\r
+ </callout>\r
+ <callout arearefs="CO6-2">\r
+ <simpara>\r
+ Registering the method as a streaming method instructs OpenSRF to also\r
+ register an atomic variant (<literal>opensrf.simple-text.split.atomic</literal>).\r
+ </simpara>\r
+ </callout>\r
+ </calloutlist>\r
+ </simplesect>\r
+ <simplesect id="_error_warning_info_debug">\r
+ <title>Error! Warning! Info! Debug!</title>\r
+ <simpara>As hard as it may be to believe, it is true: applications sometimes do not\r
+ behave in the expected manner, particularly when they are still under\r
+ development. The service language bindings for OpenSRF include integrated\r
+ support for logging messages at the levels of ERROR, WARNING, INFO, DEBUG, and\r
+ the extremely verbose INTERNAL to either a local file or to a syslogger\r
+ service. The destination of the log files, and the level of verbosity to be\r
+ logged, is set in the <literal>opensrf_core.xml</literal> configuration file. To add logging to\r
+ our Perl example, we just have to add the <literal>OpenSRF::Utils::Logger</literal> package to our\r
+ list of used Perl modules, then invoke the logger at the desired logging level.</simpara>\r
+ <simpara>You can include many calls to the OpenSRF logger; only those that are higher\r
+ than your configured logging level will actually hit the log. The following\r
+ example exercises all of the available logging levels in OpenSRF:</simpara>\r
+<programlisting language="perl" linenumbering="unnumbered">\r
+use OpenSRF::Utils::Logger;\r
+my $logger = OpenSRF::Utils::Logger;\r
+# some code in some function\r
+{\r
+ $logger->error("Hmm, something bad DEFINITELY happened!");\r
+ $logger->warn("Hmm, something bad might have happened.");\r
+ $logger->info("Something happened.");\r
+ $logger->debug("Something happened; here are some more details.");\r
+ $logger->internal("Something happened; here are all the gory details.")\r
+}\r
+</programlisting>\r
+ <simpara>If you call the mythical OpenSRF method containing the preceding OpenSRF logger\r
+ statements on a system running at the default logging level of INFO, you will\r
+ only see the INFO, WARN, and ERR messages, as follows:</simpara>\r
+ <formalpara><title>Results of logging calls at the default level of INFO</title><para>\r
+<screen>\r
+[2010-03-17 22:27:30] opensrf.simple-text [ERR :5681:SimpleText.pm:277:] \r
+[2010-03-17 22:27:30] opensrf.simple-text [WARN:5681:SimpleText.pm:278:] \r
+[2010-03-17 22:27:30] opensrf.simple-text [INFO:5681:SimpleText.pm:279:] \r
+</screen>\r
+ </para></formalpara>\r
+ <simpara>If you then increase the the logging level to INTERNAL (5), the logs will\r
+ contain much more information, as follows:</simpara>\r
+ <formalpara><title>Results of logging calls at the default level of INTERNAL</title><para>\r
+<screen>\r
+[2010-03-17 22:48:11] opensrf.simple-text [ERR :5934:SimpleText.pm:277:] \r
+[2010-03-17 22:48:11] opensrf.simple-text [WARN:5934:SimpleText.pm:278:] \r
+[2010-03-17 22:48:11] opensrf.simple-text [INFO:5934:SimpleText.pm:279:] \r
+[2010-03-17 22:48:11] opensrf.simple-text [DEBG:5934:SimpleText.pm:280:] \r
+[2010-03-17 22:48:11] opensrf.simple-text [INTL:5934:SimpleText.pm:281:] \r
+[2010-03-17 22:48:11] opensrf.simple-text [ERR :5934:SimpleText.pm:283:] \r
+[2010-03-17 22:48:21] opensrf.simple-text [INTL:5934:Cache.pm:125:] \r
+[2010-03-17 22:48:21] opensrf.simple-text [DEBG:5934:Application.pm:579:] \r
+[2010-03-17 22:48:21] opensrf.simple-text [DEBG:5934:Application.pm:586:] \r
+[2010-03-17 22:48:21] opensrf.simple-text [DEBG:5934:Application.pm:190:] \r
+[2010-03-17 22:48:21] opensrf.simple-text [INTL:5934:AppSession.pm:780:] Calling queue_wait(0)\r
+[2010-03-17 22:48:21] opensrf.simple-text [INTL:5934:AppSession.pm:769:] Resending...0\r
+[2010-03-17 22:48:21] opensrf.simple-text [INTL:5934:AppSession.pm:450:] In send\r
+[2010-03-17 22:48:21] opensrf.simple-text [DEBG:5934:AppSession.pm:506:] \r
+[2010-03-17 22:48:21] opensrf.simple-text [DEBG:5934:AppSession.pm:506:] \r
+...\r
+</screen>\r
+ </para></formalpara>\r
+ <simpara>To see everything that is happening in OpenSRF, try leaving your logging level\r
+ set to INTERNAL for a few minutes - just ensure that you have a lot of free disk\r
+ space available if you have a moderately busy system!</simpara>\r
+ </simplesect>\r
+ <simplesect id="_caching_results_one_secret_of_scalability">\r
+ <title>Caching results: one secret of scalability</title>\r
+ <indexterm><primary>search results</primary><secondary>caching</secondary></indexterm>\r
+ <simpara>If you have ever used an application that depends on a remote Web service\r
+ outside of your control — say, if you need to retrieve results from a\r
+ microblogging service — you know the pain of latency and dependability (or the\r
+ lack thereof). To improve the response time for OpenSRF services, you can take\r
+ advantage of the support offered by the <literal>OpenSRF::Utils::Cache</literal> module for\r
+ communicating with a local instance or cluster of <literal>memcache</literal> daemons to store\r
+ and retrieve persistent values. The following example demonstrates caching\r
+ by sleeping for 10 seconds the first time it receives a given cache key and\r
+ cannot retrieve a corresponding value from the cache:</simpara>\r
+ <formalpara><title>Simple caching OpenSRF service</title><para>\r
+<programlisting language="perl" linenumbering="unnumbered">\r
+use OpenSRF::Utils::Cache;<co id="CO7-1"/>\r
+sub test_cache {\r
+ my $self = shift;\r
+ my $conn = shift;\r
+ my $test_key = shift;\r
+ my $cache = OpenSRF::Utils::Cache->new('global'); <co id="CO7-2"/>\r
+ my $cache_key = "opensrf.simple-text.test_cache.$test_key"; <co id="CO7-3"/>\r
+ my $result = $cache->get_cache($cache_key) || undef; <co id="CO7-4"/>\r
+ if ($result) {\r
+ $logger->info("Resolver found a cache hit");\r
+ return $result;\r
+ }\r
+ sleep 10; <co id="CO7-5"/>\r
+ my $cache_timeout = 300; <co id="CO7-6"/>\r
+ $cache->put_cache($cache_key, "here", $cache_timeout); <co id="CO7-7"/>\r
+ return "There was no cache hit.";\r
+}\r
+</programlisting>\r
+ </para></formalpara>\r
+ <calloutlist>\r
+ <callout arearefs="CO7-1">\r
+ <simpara>\r
+ The OpenSRF::Utils::Cache module provides access to the built-in caching\r
+ support in OpenSRF.\r
+ </simpara>\r
+ </callout>\r
+ <callout arearefs="CO7-2">\r
+ <simpara>\r
+ The constructor for the cache object accepts a single argument to define\r
+ the cache type for the object. Each cache type can use a separate <literal>memcache</literal>\r
+ server to keep the caches separated. Most Evergreen services use the <literal>global</literal>\r
+ cache, while the <literal>anon</literal> cache is used for Web sessions.\r
+ </simpara>\r
+ </callout>\r
+ <callout arearefs="CO7-3">\r
+ <simpara>\r
+ The cache key is simply a string that uniquely identifies the value you\r
+ want to store or retrieve. This line creates a cache key based on the OpenSRF\r
+ method name and request input value.\r
+ </simpara>\r
+ </callout>\r
+ <callout arearefs="CO7-4">\r
+ <simpara>\r
+ The <literal>get_cache()</literal> method checks to see if the cache key already exists. If\r
+ a matching key is found, the service immediately returns the stored value.\r
+ </simpara>\r
+ </callout>\r
+ <callout arearefs="CO7-5">\r
+ <simpara>\r
+ If the cache key does not exist, the code sleeps for 10 seconds to\r
+ simulate a call to a slow remote Web service or an intensive process.\r
+ </simpara>\r
+ </callout>\r
+ <callout arearefs="CO7-6">\r
+ <simpara>\r
+ The <literal>$cache_timeout</literal> variable represents a value for the lifetime of the\r
+ cache key in seconds.\r
+ </simpara>\r
+ </callout>\r
+ <callout arearefs="CO7-7">\r
+ <simpara>\r
+ After the code retrieves its value (or, in the case of this example,\r
+ finishes sleeping), it creates the cache entry by calling the <literal>put_cache()</literal>\r
+ method. The method accepts three arguments: the cache key, the value to be\r
+ stored ("here"), and the timeout value in seconds to ensure that we do not\r
+ return stale data on subsequent calls.\r
+ </simpara>\r
+ </callout>\r
+ </calloutlist>\r
+ </simplesect>\r
+ <simplesect id="_initializing_the_service_and_its_children_child_labour">\r
+ <title>Initializing the service and its children: child labour</title>\r
+ <simpara>When an OpenSRF service is started, it looks for a procedure called\r
+ <literal>initialize()</literal> to set up any global variables shared by all of the children of\r
+ the service. The <literal>initialize()</literal> procedure is typically used to retrieve\r
+ configuration settings from the <literal>opensrf.xml</literal> file.</simpara>\r
+ <simpara>An OpenSRF service spawns one or more children to actually do the work\r
+ requested by callers of the service. For every child process an OpenSRF service\r
+ spawns, the child process clones the parent environment and then each child\r
+ process runs the <literal>child_init()</literal> process (if any) defined in the OpenSRF service\r
+ to initialize any child-specific settings.</simpara>\r
+ <simpara>When the OpenSRF service kills a child process, it invokes the <literal>child_exit()</literal>\r
+ procedure (if any) to clean up any resources associated with the child process.\r
+ Similarly, when the OpenSRF service is stopped, it calls the <literal>DESTROY()</literal>\r
+ procedure to clean up any remaining resources.</simpara>\r
+ </simplesect>\r
+ <simplesect id="_retrieving_configuration_settings">\r
+ <title>Retrieving configuration settings</title>\r
+ <simpara>The settings for OpenSRF services are maintained in the <literal>opensrf.xml</literal> XML\r
+ configuration file. The structure of the XML document consists of a root\r
+ element <literal><opensrf></literal> containing two child elements:</simpara>\r
+ <itemizedlist>\r
+ <listitem>\r
+ <simpara>\r
+ The <literal><default></literal> element contains an <literal><apps></literal> element describing all\r
+ OpenSRF services running on this system — see <xref linkend="serviceRegistration"/> --, as\r
+ well as any other arbitrary XML descriptions required for global configuration\r
+ purposes. For example, Evergreen uses this section for email notification and\r
+ inter-library patron privacy settings.\r
+ </simpara>\r
+ </listitem>\r
+ <listitem>\r
+ <simpara>\r
+ The <literal><hosts></literal> element contains one element per host that participates in\r
+ this OpenSRF system. Each host element must include an <literal><activeapps></literal> element\r
+ that lists all of the services to start on this host when the system starts\r
+ up. Each host element can optionally override any of the default settings.\r
+ </simpara>\r
+ </listitem>\r
+ </itemizedlist>\r
+ <simpara>OpenSRF includes a service named <literal>opensrf.settings</literal> to provide distributed\r
+ cached access to the configuration settings with a simple API:</simpara>\r
+ <itemizedlist>\r
+ <listitem>\r
+ <simpara>\r
+ <literal>opensrf.settings.default_config.get</literal> accepts zero arguments and returns\r
+ the complete set of default settings as a JSON document.\r
+ </simpara>\r
+ </listitem>\r
+ <listitem>\r
+ <simpara>\r
+ <literal>opensrf.settings.host_config.get</literal> accepts one argument (hostname) and\r
+ returns the complete set of settings, as customized for that hostname, as a\r
+ JSON document.\r
+ </simpara>\r
+ </listitem>\r
+ <listitem>\r
+ <simpara>\r
+ <literal>opensrf.settings.xpath.get</literal> accepts one argument (an\r
+ <ulink url="http://www.w3.org/TR/xpath/">XPath</ulink> expression) and returns the portion of\r
+ the configuration file that matches the expression as a JSON document.\r
+ </simpara>\r
+ </listitem>\r
+ </itemizedlist>\r
+ <simpara>For example, to determine whether an Evergreen system uses the opt-in\r
+ support for sharing patron information between libraries, you could either\r
+ invoke the <literal>opensrf.settings.default_config.get</literal> method and parse the\r
+ JSON document to determine the value, or invoke the <literal>opensrf.settings.xpath.get</literal>\r
+ method with the XPath <literal>/opensrf/default/share/user/opt_in</literal> argument to\r
+ retrieve the value directly.</simpara>\r
+ <simpara>In practice, OpenSRF includes convenience libraries in all of its client\r
+ language bindings to simplify access to configuration values. C offers\r
+ osrfConfig.c, Perl offers <literal>OpenSRF::Utils::SettingsClient</literal>, Java offers\r
+ <literal>org.opensrf.util.SettingsClient</literal>, and Python offers <literal>osrf.set</literal>. These\r
+ libraries locally cache the configuration file to avoid network roundtrips for\r
+ every request and enable the developer to request specific values without\r
+ having to manually construct XPath expressions.</simpara>\r
+ </simplesect>\r
+ </section>\r
+ <section id="_getting_under_the_covers_with_opensrf">\r
+ <title>OpenSRF Communication Flows</title>\r
+ <indexterm><primary>OpenSRF</primary><secondary>Communication Flows</secondary></indexterm>\r
+ <simpara>Now that you have seen that it truly is easy to create an OpenSRF service, we\r
+ can take a look at what is going on under the covers to make all of this work\r
+ for you.</simpara>\r
+ <simplesect id="_get_on_the_messaging_bus_safely">\r
+ <title>Get on the messaging bus - safely</title>\r
+ <simpara>One of the core innovations of OpenSRF was to use the Extensible Messaging and\r
+ Presence Protocol (XMPP, more colloquially known as Jabber) as the messaging\r
+ bus that ties OpenSRF services together across servers. XMPP is an "XML\r
+ protocol for near-real-time messaging, presence, and request-response services"\r
+ (<ulink url="http://www.ietf.org/rfc/rfc3920.txt">http://www.ietf.org/rfc/rfc3920.txt</ulink>) that OpenSRF relies on to handle most of\r
+ the complexity of networked communications. OpenSRF requres an XMPP server\r
+ that supports multiple domains such as <ulink url="http://www.ejabberd.im/">ejabberd</ulink>.\r
+ Multiple domain support means that a single server can support XMPP virtual\r
+ hosts with separate sets of users and access privileges per domain. By\r
+ routing communications through separate public and private XMPP domains,\r
+ OpenSRF services gain an additional layer of security.</simpara>\r
+ <simpara>The <ulink url="http://evergreen-ils.org/dokuwiki/doku.php?id=opensrf:1.2:install">OpenSRF\r
+ installation documentation</ulink> instructs you to create two separate hostnames\r
+ (<literal>private.localhost</literal> and <literal>public.localhost</literal>) to use as XMPP domains. OpenSRF\r
+ can control access to its services based on the domain of the client and\r
+ whether a given service allows access from clients on the public domain. When\r
+ you start OpenSRF, the first XMPP clients that connect to the XMPP server are\r
+ the OpenSRF public and private <emphasis>routers</emphasis>. OpenSRF routers maintain a list of\r
+ available services and connect clients to available services. When an OpenSRF\r
+ service starts, it establishes a connection to the XMPP server and registers\r
+ itself with the private router. The OpenSRF configuration contains a list of\r
+ public OpenSRF services, each of which must also register with the public\r
+ router.</simpara>\r
+ </simplesect>\r
+ <simplesect id="_opensrf_communication_flows_over_xmpp">\r
+ <title>OpenSRF communication flows over XMPP</title>\r
+ <indexterm><primary>XMPP</primary></indexterm>\r
+ <simpara>In a minimal OpenSRF deployment, two XMPP users named "router" connect to the\r
+ XMPP server, with one connected to the private XMPP domain and one connected to\r
+ the public XMPP domain. Similarly, two XMPP users named "opensrf" connect to\r
+ the XMPP server via the private and public XMPP domains. When an OpenSRF\r
+ service is started, it uses the "opensrf" XMPP user to advertise its\r
+ availability with the corresponding router on that XMPP domain; the XMPP server\r
+ automatically assigns a Jabber ID (<emphasis>JID</emphasis>) based on the client hostname to each\r
+ service’s listener process and each connected drone process waiting to carry\r
+ out requests. When an OpenSRF router receives a request to invoke a method on a\r
+ given service, it connects the requester to the next available listener in the\r
+ list of registered listeners for that service.</simpara>\r
+ <simpara>Services and clients connect to the XMPP server using a single set of XMPP\r
+ client credentials (for example, <literal>opensrf@private.localhost</literal>), but use XMPP\r
+ resource identifiers to differentiate themselves in the JID for each\r
+ connection. For example, the JID for a copy of the <literal>opensrf.simple-text</literal>\r
+ service with process ID <literal>6285</literal> that has connected to the <literal>private.localhost</literal>\r
+ domain using the <literal>opensrf</literal> XMPP client credentials could be\r
+ <literal>opensrf@private.localhost/opensrf.simple-text_drone_at_localhost_6285</literal>. By\r
+ convention, the user name for OpenSRF clients is <literal>opensrf</literal>, and the user name\r
+ for OpenSRF routers is <literal>router</literal>, so the XMPP server for OpenSRF will have four\r
+ separate users registered:\r
+ * <literal>opensrf@private.localhost</literal> is an OpenSRF client that connects with these\r
+ credentials and which can access any OpenSRF service.\r
+ * <literal>opensrf@public.localhost</literal> is an OpenSRF client that connects with these\r
+ credentials and which can only access OpenSRF services that have registered\r
+ with the public router.\r
+ * <literal>router@private.localhost</literal> is the private OpenSRF router with which all\r
+ services register.\r
+ * <literal>router@public.localhost</literal> is the public OpenSRF router with which only\r
+ services that must be publicly accessible register.</simpara>\r
+ <simpara>All OpenSRF services automatically register themselves with the private XMPP\r
+ domain, but only those services that register themselves with the public XMPP\r
+ domain can be invoked from public OpenSRF clients. The OpenSRF client and\r
+ router user names, passwords, and domain names, along with the list of services\r
+ that should be public, are contained in the <literal>opensrf_core.xml</literal> configuration\r
+ file.</simpara>\r
+ </simplesect>\r
+ <simplesect id="OpenSRFOverHTTP">\r
+ <title>OpenSRF communication flows over HTTP</title>\r
+ <indexterm><primary>HTTP</primary><secondary>translator</secondary></indexterm>\r
+ <simpara>In some contexts, access to a full XMPP client is not a practical option. For\r
+ example, while XMPP clients have been implemented in JavaScript, you might\r
+ be concerned about browser compatibility and processing overhead - or you might\r
+ want to issue OpenSRF requests from the command line with <literal>curl</literal>. Fortunately,\r
+ any OpenSRF service registered with the public router is accessible via the\r
+ OpenSRF HTTP Translator. The OpenSRF HTTP Translator implements the\r
+ <ulink url="http://www.open-ils.org/dokuwiki/doku.php?id=opensrf_over_http">OpenSRF-over-HTTP\r
+ proposed specification</ulink> as an Apache module that translates HTTP requests into\r
+ OpenSRF requests and returns OpenSRF results as HTTP results to the initiating\r
+ HTTP client.</simpara>\r
+ <formalpara><title>Issuing an HTTP POST request to an OpenSRF method via the OpenSRF HTTP Translator</title><para>\r
+<programlisting language="bash" linenumbering="unnumbered">\r
+# curl request broken up over multiple lines for legibility\r
+curl -H "X-OpenSRF-service: opensrf.simple-text"<co id="CO8-1"/>\r
+ --data 'osrf-msg=[ \<co id="CO8-2"/>\r
+ {"__c":"osrfMessage","__p":{"threadTrace":0,"locale":"en-CA", <co id="CO8-3"/>\r
+ "type":"REQUEST","payload": {"__c":"osrfMethod","__p": \r
+ {"method":"opensrf.simple-text.reverse","params":["foobar"]} \r
+ }} \r
+ }]' \r
+http://localhost/osrf-http-translator <co id="CO8-4"/>\r
+</programlisting>\r
+ </para></formalpara>\r
+ <calloutlist>\r
+ <callout arearefs="CO8-1">\r
+ <simpara>\r
+ The <literal>X-OpenSRF-service</literal> header identifies the OpenSRF service of interest.\r
+ </simpara>\r
+ </callout>\r
+ <callout arearefs="CO8-2">\r
+ <simpara>\r
+ The POST request consists of a single parameter, the <literal>osrf-msg</literal> value,\r
+ which contains a JSON array.\r
+ </simpara>\r
+ </callout>\r
+ <callout arearefs="CO8-3">\r
+ <simpara>\r
+ The first object is an OpenSRF message (<literal>"__c":"osrfMessage"</literal>) with a set of\r
+ parameters (<literal>"__p":{}</literal>).\r
+ </simpara>\r
+ <itemizedlist>\r
+ <listitem>\r
+ <simpara>\r
+ The identifier for the request (<literal>"threadTrace":0</literal>); this value is echoed\r
+ back in the result.\r
+ </simpara>\r
+ </listitem>\r
+ <listitem>\r
+ <simpara>\r
+ The message type (<literal>"type":"REQUEST"</literal>).\r
+ </simpara>\r
+ </listitem>\r
+ <listitem>\r
+ <simpara>\r
+ The locale for the message; if the OpenSRF method is locale-sensitive, it\r
+ can check the locale for each OpenSRF request and return different information\r
+ depending on the locale.\r
+ </simpara>\r
+ </listitem>\r
+ <listitem>\r
+ <simpara>\r
+ The payload of the message (<literal>"payload":{}</literal>) containing the OpenSRF method\r
+ request (<literal>"__c":"osrfMethod"</literal>) and its parameters (<literal>"__p:"{}</literal>).\r
+ </simpara>\r
+ <itemizedlist>\r
+ <listitem>\r
+ <simpara>\r
+ The method name for the request (<literal>"method":"opensrf.simple-text.reverse"</literal>).\r
+ </simpara>\r
+ </listitem>\r
+ <listitem>\r
+ <simpara>\r
+ A set of JSON parameters to pass to the method (<literal>"params":["foobar"]</literal>); in\r
+ this case, a single string <literal>"foobar"</literal>.\r
+ </simpara>\r
+ </listitem>\r
+ </itemizedlist>\r
+ </listitem>\r
+ </itemizedlist>\r
+ </callout>\r
+ <callout arearefs="CO8-4">\r
+ <simpara>\r
+ The URL on which the OpenSRF HTTP translator is listening,\r
+ <literal>/osrf-http-translator</literal> is the default location in the Apache example\r
+ configuration files shipped with the OpenSRF source, but this is configurable.\r
+ </simpara>\r
+ </callout>\r
+ </calloutlist>\r
+ <formalpara><title>Results from an HTTP POST request to an OpenSRF method via the OpenSRF HTTP Translator</title><para>\r
+<programlisting language="bash" linenumbering="unnumbered">\r
+# HTTP response broken up over multiple lines for legibility\r
+[{"__c":"osrfMessage","__p": <co id="CO9-1"/>\r
+ {"threadTrace":0, "payload": <co id="CO9-2"/>\r
+ {"__c":"osrfResult","__p": <co id="CO9-3"/>\r
+ {"status":"OK","content":"raboof","statusCode":200} <co id="CO9-4"/>\r
+ },"type":"RESULT","locale":"en-CA" <co id="CO9-5"/>\r
+ }\r
+},\r
+{"__c":"osrfMessage","__p": <co id="CO9-6"/>\r
+ {"threadTrace":0,"payload": <co id="CO9-7"/>\r
+ {"__c":"osrfConnectStatus","__p": <co id="CO9-8"/>\r
+ {"status":"Request Complete","statusCode":205}<co id="CO9-9"/>\r
+ },"type":"STATUS","locale":"en-CA" <co id="CO9-10"/>\r
+ }\r
+}]\r
+</programlisting>\r
+ </para></formalpara>\r
+ <calloutlist>\r
+ <callout arearefs="CO9-1">\r
+ <simpara>\r
+ The OpenSRF HTTP Translator returns an array of JSON objects in its\r
+ response. Each object in the response is an OpenSRF message\r
+ (<literal>"__c":"osrfMessage"</literal>) with a collection of response parameters (<literal>"__p":</literal>).\r
+ </simpara>\r
+ </callout>\r
+ <callout arearefs="CO9-2">\r
+ <simpara>\r
+ The OpenSRF message identifier (<literal>"threadTrace":0</literal>) confirms that this\r
+ message is in response to the request matching the same identifier.\r
+ </simpara>\r
+ </callout>\r
+ <callout arearefs="CO9-3">\r
+ <simpara>\r
+ The message includes a payload JSON object (<literal>"payload":</literal>) with an OpenSRF\r
+ result for the request (<literal>"__c":"osrfResult"</literal>).\r
+ </simpara>\r
+ </callout>\r
+ <callout arearefs="CO9-4">\r
+ <simpara>\r
+ The result includes a status indicator string (<literal>"status":"OK"</literal>), the content\r
+ of the result response - in this case, a single string "raboof"\r
+ (<literal>"content":"raboof"</literal>) - and an integer status code for the request\r
+ (<literal>"statusCode":200</literal>).\r
+ </simpara>\r
+ </callout>\r
+ <callout arearefs="CO9-5">\r
+ <simpara>\r
+ The message also includes the message type (<literal>"type":"RESULT"</literal>) and the\r
+ message locale (<literal>"locale":"en-CA"</literal>).\r
+ </simpara>\r
+ </callout>\r
+ <callout arearefs="CO9-6">\r
+ <simpara>\r
+ The second message in the set of results from the response.\r
+ </simpara>\r
+ </callout>\r
+ <callout arearefs="CO9-7">\r
+ <simpara>\r
+ Again, the message identifier confirms that this message is in response to\r
+ a particular request.\r
+ </simpara>\r
+ </callout>\r
+ <callout arearefs="CO9-8">\r
+ <simpara>\r
+ The payload of the message denotes that this message is an\r
+ OpenSRF connection status message (<literal>"__c":"osrfConnectStatus"</literal>), with some\r
+ information about the particular OpenSRF connection that was used for this\r
+ request.\r
+ </simpara>\r
+ </callout>\r
+ <callout arearefs="CO9-9">\r
+ <simpara>\r
+ The response parameters for an OpenSRF connection status message include a\r
+ verbose status (<literal>"status":"Request Complete"</literal>) and an integer status code for\r
+ the connection status (`"statusCode":205).\r
+ </simpara>\r
+ </callout>\r
+ <callout arearefs="CO9-10">\r
+ <simpara>\r
+ The message also includes the message type (<literal>"type":"RESULT"</literal>) and the\r
+ message locale (<literal>"locale":"en-CA"</literal>).\r
+ </simpara>\r
+ </callout>\r
+ </calloutlist>\r
+ <tip><simpara>Before adding a new public OpenSRF service, ensure that it does\r
+ not introduce privilege escalation or unchecked access to data. For example,\r
+ the Evergreen <literal>open-ils.cstore</literal> private service is an object-relational mapper\r
+ that provides read and write access to the entire Evergreen database, so it\r
+ would be catastrophic to expose that service publicly. In comparison, the\r
+ Evergreen <literal>open-ils.pcrud</literal> public service offers the same functionality as\r
+ <literal>open-ils.cstore</literal> to any connected HTTP client or OpenSRF client, but the\r
+ additional authentication and authorization layer in <literal>open-ils.pcrud</literal> prevents\r
+ unchecked access to Evergreen’s data.</simpara></tip>\r
+ </simplesect>\r
+ <simplesect id="_stateless_and_stateful_connections">\r
+ <title>Stateless and stateful connections</title>\r
+ <simpara>OpenSRF supports both <emphasis>stateless</emphasis> and <emphasis>stateful</emphasis> connections. When an OpenSRF\r
+ client issues a <literal>REQUEST</literal> message in a <emphasis>stateless</emphasis> connection, the router\r
+ forwards the request to the next available service and the service returns the\r
+ result directly to the client.</simpara>\r
+ \r
+ <simpara>When an OpenSRF client issues a <literal>CONNECT</literal> message to create a <emphasis>stateful</emphasis> conection, the\r
+ router returns the Jabber ID of the next available service to the client so\r
+ that the client can issue one or more <literal>REQUEST</literal> message directly to that\r
+ particular service and the service will return corresponding <literal>RESULT</literal> messages\r
+ directly to the client. Until the client issues a <literal>DISCONNECT</literal> message, that\r
+ particular service is only available to the requesting client. Stateful connections\r
+ are useful for clients that need to make many requests from a particular service,\r
+ as it avoids the intermediary step of contacting the router for each request, as\r
+ well as for operations that require a controlled sequence of commands, such as a\r
+ set of database INSERT, UPDATE, and DELETE statements within a transaction.</simpara>\r
+ \r
+ </simplesect>\r
+ <simplesect id="_message_body_format">\r
+ <title>Message body format</title>\r
+ <simpara>OpenSRF was an early adopter of JavaScript Object Notation (JSON). While XMPP\r
+ is an XML protocol, the Evergreen developers recognized that the compactness of\r
+ the JSON format offered a significant reduction in bandwidth for the volume of\r
+ messages that would be generated in an application of that size. In addition,\r
+ the ability of languages such as JavaScript, Perl, and Python to generate\r
+ native objects with minimal parsing offered an attractive advantage over\r
+ invoking an XML parser for every message. Instead, the body of the XMPP message\r
+ is a simple JSON structure. For a simple request, like the following example\r
+ that simply reverses a string, it looks like a significant overhead: but we get\r
+ the advantages of locale support and tracing the request from the requester\r
+ through the listener and responder (drone).</simpara>\r
+ <formalpara><title>A request for opensrf.simple-text.reverse("foobar"):</title><para>\r
+<programlisting language="xml" linenumbering="unnumbered">\r
+<message from='router@private.localhost/opensrf.simple-text'\r
+ to='opensrf@private.localhost/opensrf.simple-text_listener_at_localhost_6275'\r
+ router_from='opensrf@private.localhost/_karmic_126678.3719_6288'\r
+ router_to='' router_class='' router_command='' osrf_xid=''\r
+>\r
+ <thread>1266781414.366573.12667814146288</thread>\r
+ <body>\r
+[\r
+ {"__c":"osrfMessage","__p":\r
+ {"threadTrace":"1","locale":"en-US","type":"REQUEST","payload":\r
+ {"__c":"osrfMethod","__p":\r
+ {"method":"opensrf.simple-text.reverse","params":["foobar"]}\r
+ }\r
+ }\r
+ }\r
+]\r
+ </body>\r
+</message>\r
+</programlisting>\r
+ </para></formalpara>\r
+ <formalpara><title>A response from opensrf.simple-text.reverse("foobar")</title><para>\r
+<programlisting language="xml" linenumbering="unnumbered">\r
+<message from='opensrf@private.localhost/opensrf.simple-text_drone_at_localhost_6285'\r
+ to='opensrf@private.localhost/_karmic_126678.3719_6288'\r
+ router_command='' router_class='' osrf_xid=''\r
+>\r
+ <thread>1266781414.366573.12667814146288</thread>\r
+ <body>\r
+[\r
+ {"__c":"osrfMessage","__p":\r
+ {"threadTrace":"1","payload":\r
+ {"__c":"osrfResult","__p":\r
+ {"status":"OK","content":"raboof","statusCode":200}\r
+ } ,"type":"RESULT","locale":"en-US"}\r
+ },\r
+ {"__c":"osrfMessage","__p":\r
+ {"threadTrace":"1","payload":\r
+ {"__c":"osrfConnectStatus","__p":\r
+ {"status":"Request Complete","statusCode":205}\r
+ },"type":"STATUS","locale":"en-US"}\r
+ }\r
+]\r
+ </body>\r
+</message>\r
+</programlisting>\r
+ </para></formalpara>\r
+ <simpara>The content of the <literal><body></literal> element of the OpenSRF request and result should\r
+ look familiar; they match the structure of the <link linkend="OpenSRFOverHTTP">OpenSRF over HTTP examples</link> that we previously dissected.</simpara>\r
+ </simplesect>\r
+ <simplesect id="_registering_opensrf_methods_in_depth">\r
+ <title>Registering OpenSRF methods in depth</title>\r
+ <simpara>Let’s explore the call to <literal>__PACKAGE__->register_method()</literal>; most of the members\r
+ of the hash are optional, and for the sake of brevity we omitted them in the\r
+ previous example. As we have seen in the results of the <link linkend="opensrfIntrospection">introspection call</link>, a\r
+ verbose registration method call is recommended to better enable the internal\r
+ documentation. Here is the complete set of members that you should pass to\r
+ <literal>__PACKAGE__->register_method()</literal>:</simpara>\r
+ <itemizedlist>\r
+ <listitem>\r
+ <simpara>\r
+ The <literal>method</literal> member specifies the name of the procedure in this module that is being registered as an OpenSRF method.\r
+ </simpara>\r
+ </listitem>\r
+ <listitem>\r
+ <simpara>\r
+ The <literal>api_name</literal> member specifies the invocable name of the OpenSRF method; by convention, the OpenSRF service name is used as the prefix.\r
+ </simpara>\r
+ </listitem>\r
+ <listitem>\r
+ <simpara>\r
+ The optional <literal>api_level</literal> member can be used for versioning the methods to allow the use of a deprecated API, but in practical use is always 1.\r
+ </simpara>\r
+ </listitem>\r
+ <listitem>\r
+ <simpara>\r
+ The optional <literal>argc</literal> member specifies the minimal number of arguments that the method expects.\r
+ </simpara>\r
+ </listitem>\r
+ <listitem>\r
+ <simpara>\r
+ The optional <literal>stream</literal> member, if set to any value, specifies that the method supports returning multiple values from a single call to \r
+ subsequent requests. OpenSRF automatically creates a corresponding method with ".atomic" appended to its name that returns the complete set of results in a \r
+ single request. Streaming methods are useful if you are returning hundreds of records and want to act on the results as they return.\r
+ </simpara>\r
+ </listitem>\r
+ <listitem>\r
+ <simpara>\r
+ The optional <literal>signature</literal> member is a hash that describes the method’s purpose, arguments, and return value.\r
+ </simpara>\r
+ <itemizedlist>\r
+ <listitem>\r
+ <simpara>\r
+ The <literal>desc</literal> member of the <literal>signature</literal> hash describes the method’s purpose.\r
+ </simpara>\r
+ </listitem>\r
+ <listitem>\r
+ <simpara>\r
+ The <literal>params</literal> member of the <literal>signature</literal> hash is an array of hashes in which each array element describes the corresponding method \r
+ argument in order.\r
+ </simpara>\r
+ <itemizedlist>\r
+ <listitem>\r
+ <simpara>\r
+ The <literal>name</literal> member of the argument hash specifies the name of the argument.\r
+ </simpara>\r
+ </listitem>\r
+ <listitem>\r
+ <simpara>\r
+ The <literal>desc</literal> member of the argument hash describes the argument’s purpose.\r
+ </simpara>\r
+ </listitem>\r
+ <listitem>\r
+ <simpara>\r
+ The <literal>type</literal> member of the argument hash specifies the data type of the argument: for example, string, integer, boolean, number, array, or hash.\r
+ </simpara>\r
+ </listitem>\r
+ </itemizedlist>\r
+ </listitem>\r
+ <listitem>\r
+ <simpara>\r
+ The <literal>return</literal> member of the <literal>signature</literal> hash is a hash that describes the return value of the method.\r
+ </simpara>\r
+ <itemizedlist>\r
+ <listitem>\r
+ <simpara>\r
+ The <literal>desc</literal> member of the <literal>return</literal> hash describes the return value.\r
+ </simpara>\r
+ </listitem>\r
+ <listitem>\r
+ <simpara>\r
+ The <literal>type</literal> member of the <literal>return</literal> hash specifies the data type of the return value: for example, string, integer, boolean, number, \r
+ array, or hash.\r
+ </simpara>\r
+ </listitem>\r
+ </itemizedlist>\r
+ </listitem>\r
+ </itemizedlist>\r
+ </listitem>\r
+ </itemizedlist>\r
+ </simplesect>\r
+ </section>\r
+ <section id="_evergreen_specific_opensrf_services">\r
+ <title>Evergreen-specific OpenSRF services</title>\r
+ <simpara>Evergreen is currently the primary showcase for the use of OpenSRF as an\r
+ application architecture. Evergreen 1.6.1 includes the following\r
+ set of OpenSRF services:</simpara>\r
+ <itemizedlist>\r
+ <listitem>\r
+ <simpara>\r
+ The <systemitem class="service">open-ils.actor</systemitem> service supports common tasks for working with user\r
+ accounts and libraries.\r
+ </simpara>\r
+ </listitem>\r
+ <listitem>\r
+ <simpara>\r
+ The <systemitem class="service">open-ils.auth</systemitem> service supports authentication of Evergreen users.\r
+ </simpara>\r
+ </listitem>\r
+ <listitem>\r
+ <simpara>\r
+ The <systemitem class="service">open-ils.booking</systemitem> service supports the management of reservations\r
+ for bookable items.\r
+ </simpara>\r
+ </listitem>\r
+ <listitem>\r
+ <simpara>\r
+ The <systemitem class="service">open-ils.cat</systemitem> service supports common cataloging tasks, such as\r
+ creating, modifying, and merging bibliographic and authority records.\r
+ </simpara>\r
+ </listitem>\r
+ <listitem>\r
+ <simpara>\r
+ The <systemitem class="service">open-ils.circ</systemitem> service supports circulation tasks such as checking\r
+ out items and calculating due dates.\r
+ </simpara>\r
+ </listitem>\r
+ <listitem>\r
+ <simpara>\r
+ The <systemitem class="service">open-ils.collections</systemitem> service supports tasks that assist collections\r
+ agencies in contacting users with outstanding fines above a certain\r
+ threshold.\r
+ </simpara>\r
+ </listitem>\r
+ <listitem>\r
+ <simpara>\r
+ The <systemitem class="service">open-ils.cstore</systemitem> private service supports unrestricted access to\r
+ Evergreen fieldmapper objects.\r
+ </simpara>\r
+ </listitem>\r
+ <listitem>\r
+ <simpara>\r
+ The <systemitem class="service">open-ils.ingest</systemitem> private service supports tasks for importing\r
+ data such as bibliographic and authority records.\r
+ </simpara>\r
+ </listitem>\r
+ <listitem>\r
+ <simpara>\r
+ The <systemitem class="service">open-ils.pcrud</systemitem> service supports permission-based access to Evergreen\r
+ fieldmapper objects.\r
+ </simpara>\r
+ </listitem>\r
+ <listitem>\r
+ <simpara>\r
+ The <systemitem class="service">open-ils.penalty</systemitem> penalty service supports the calculation of\r
+ penalties for users, such as being blocked from further borrowing, for\r
+ conditions such as having too many items checked out or too many unpaid\r
+ fines.\r
+ </simpara>\r
+ </listitem>\r
+ <listitem>\r
+ <simpara>\r
+ The <systemitem class="service">open-ils.reporter</systemitem> service supports the creation and scheduling of\r
+ reports.\r
+ </simpara>\r
+ </listitem>\r
+ <listitem>\r
+ <simpara>\r
+ The <systemitem class="service">open-ils.reporter-store</systemitem> private service supports access to Evergreen\r
+ fieldmapper objects for the reporting service.\r
+ </simpara>\r
+ </listitem>\r
+ <listitem>\r
+ <simpara>\r
+ The <systemitem class="service">open-ils.search</systemitem> service supports searching across bibliographic\r
+ records, authority records, serial records, Z39.50 sources, and ZIP codes.\r
+ </simpara>\r
+ </listitem>\r
+ <listitem>\r
+ <simpara>\r
+ The <systemitem class="service">open-ils.storage</systemitem> private service supports a deprecated method of\r
+ providing access to Evergreen fieldmapper objects. Implemented in Perl,\r
+ this service has largely been replaced by the much faster C-based\r
+ <literal>open-ils.cstore</literal> service.\r
+ </simpara>\r
+ </listitem>\r
+ <listitem>\r
+ <simpara>\r
+ The <systemitem class="service">open-ils.supercat</systemitem> service supports transforms of MARC records into\r
+ other formats, such as MODS, as well as providing Atom and RSS feeds and\r
+ SRU access.\r
+ </simpara>\r
+ </listitem>\r
+ <listitem>\r
+ <simpara>\r
+ The <systemitem class="service">open-ils.trigger</systemitem> private service supports event-based triggers for\r
+ actions such as overdue and holds available notification emails.\r
+ </simpara>\r
+ </listitem>\r
+ <listitem>\r
+ <simpara>\r
+ The <systemitem class="service">open-ils.vandelay</systemitem> service supports the import and export of batches of\r
+ bibliographic and authority records.\r
+ </simpara>\r
+ </listitem>\r
+ </itemizedlist>\r
+ <simpara>Of some interest is that the <systemitem class="service">open-ils.reporter-store</systemitem> and <systemitem class="service">open-ils.cstore</systemitem>\r
+ services have identical implementations. Surfacing them as separate services\r
+ enables a deployer of Evergreen to ensure that the reporting service does not\r
+ interfere with the performance-critical <systemitem class="service">open-ils.cstore</systemitem> service. One can also\r
+ direct the reporting service to a read-only database replica to, again, avoid\r
+ interference with <systemitem class="service">open-ils.cstore</systemitem> which must write to the master database.</simpara>\r
+ <simpara>There are only a few significant services that are not built on OpenSRF in\r
+ Evergreen 1.6.0, such as the SIP and Z39.50 servers. These services implement\r
+ different protocols and build on existing daemon architectures (Simple2ZOOM\r
+ for Z39.50), but still rely on the other OpenSRF services to provide access\r
+ to the Evergreen data. The non-OpenSRF services are reasonably self-contained\r
+ and can be deployed on different servers to deliver the same sort of deployment\r
+ flexibility as OpenSRF services, but have the disadvantage of not being\r
+ integrated into the same configuration and control infrastructure as the\r
+ OpenSRF services.</simpara>\r
+ </section>\r
+</chapter>\r