]> git.evergreen-ils.org Git - working/Evergreen.git/blob - 1.6/development/workshop.xml
Fix splling and typos.
[working/Evergreen.git] / 1.6 / development / workshop.xml
1 <?xml version="1.0" encoding="UTF-8"?>\r
2 <chapter xml:id="developer_workshop" xmlns="http://docbook.org/ns/docbook" version="5.0" xml:lang="EN"\r
3     xmlns:xi="http://www.w3.org/2001/XInclude" xmlns:xlink="http://www.w3.org/1999/xlink">\r
4 \r
5 <chapterinfo>\r
6     <title>Evergreen development</title>\r
7     <date>February 2010</date>\r
8     <author>\r
9         <firstname>Dan</firstname>\r
10         <surname>Scott</surname>\r
11         <email>dscott@laurentian.ca</email>\r
12     </author>\r
13     <authorinitials>DS</authorinitials>\r
14 <revhistory><revision><revnumber>1.0</revnumber><date>February 2010</date><authorinitials>DS</authorinitials></revision></revhistory>\r
15 </chapterinfo>\r
16 <section id="_part_1_opensrf_applications">\r
17 <title>Part 1: OpenSRF applications</title>\r
18 <simpara>OpenSRF, pronounced "Open Surf", is the Open <emphasis role="strong">S</emphasis>ervice <emphasis role="strong">R</emphasis>equest\r
19 <emphasis role="strong">F</emphasis>ramework. It was designed as an architecture on which one could\r
20 easily build scalable applications.</simpara>\r
21 <section id="_introduction_to_opensrf">\r
22 <title>Introduction to OpenSRF</title>\r
23 <simpara>The framework is built on JSON-over-XMPP. XML can be used, but JSON\r
24 is much less verbose. XMPP is a standard messaging protocol that\r
25 has been used as the backbone of low-latency, high-volume\r
26 applications including instant messaging and Google Wave.</simpara>\r
27 <simpara>OpenSRF offers scalability via its clustering architecture; a service\r
28 that is a bottleneck can be moved onto its own server; or multiple\r
29 instances of the service can be run on many servers. Services can\r
30 themselves be clients of other services.</simpara>\r
31 <simpara>OpenSRF services listen at an XMPP address such as\r
32 "opensrf@private.localhost/open-ils.fielder_drone_at_localhost_7652".\r
33 The initial request from an OpenSRF client is directed to the\r
34 OpenSRF router, which determines whether the requested service is\r
35 accessible to the client (based on the public versus private domains),\r
36 and then connects the client to the service for any subsequent\r
37 communication that is required.</simpara>\r
38 <simpara>To significantly improve the speed at which request services can\r
39 respond to common requests, OpenSRF has integrated support for the\r
40 caching via the <literal>memcached</literal> daemon. For example, the contents of the\r
41 configuration files are cached by the <literal>opensrf.settings</literal> service when\r
42 that service starts, so that rather than having to parse the XML file\r
43 every time a service checks a configuration setting, the value can be\r
44 retrieved with much less overhead directly from the cache.</simpara>\r
45 <note><simpara>if you change a setting in one of those configuration files, you\r
46 must restart the <literal>opensrf.settings</literal> service to update its data. You must\r
47 then restart any of the services that make use of that setting to make\r
48 the change take effect.</simpara></note>\r
49 <simpara>Supports Perl, C, and Python as services and clients, and Java as a\r
50 client. JavaScript can access services via HTTP translator and\r
51 gateway. JSON library converts messages to/from native structures for\r
52 ease of development.</simpara>\r
53 </section>\r
54 <section id="_configuring_opensrf">\r
55 <title>Configuring OpenSRF</title>\r
56 <simpara>Walk through the configuration files, explaining <emphasis>why</emphasis> we put the values\r
57 into the files that we do:</simpara>\r
58 <itemizedlist>\r
59 <listitem>\r
60 <simpara>\r
61 opensrf_core.xml\r
62 </simpara>\r
63 <itemizedlist>\r
64 <listitem>\r
65 <simpara>\r
66 Distinguish between public and private services for security of Web-based applications.\r
67 </simpara>\r
68 </listitem>\r
69 <listitem>\r
70 <simpara>\r
71 Deprecated HTTP gateway versus OpenSRF-over-HTTP\r
72 </simpara>\r
73 </listitem>\r
74 </itemizedlist>\r
75 </listitem>\r
76 <listitem>\r
77 <simpara>\r
78 opensrf.xml\r
79 </simpara>\r
80 </listitem>\r
81 </itemizedlist>\r
82 <tip><simpara>In a clustered OpenSRF instance, these files are normally hosted on\r
83 a network share so that each member of the cluster can read them.</simpara></tip>\r
84 </section>\r
85 <section id="_starting_opensrf_services">\r
86 <title>Starting OpenSRF services</title>\r
87 <note><simpara>I won&#8217;t go through this during a live session. Perhaps I can cut this\r
88 out entirely&#8230;</simpara></note>\r
89 <simpara>Issue the following commands as the <literal>opensrf</literal> user. If you are running OpenSRF\r
90 on a single-server machine, you can use the <literal>-l</literal> flag to force the hostname\r
91 to be treated as <literal>localhost</literal>.</simpara>\r
92 <orderedlist numeration="arabic">\r
93 <listitem>\r
94 <simpara>\r
95 Start the OpenSRF router:\r
96 </simpara>\r
97 <screen>osrf_ctl.sh -a start_router</screen>\r
98 <important><simpara>The router must only run on a single machine in a given brick.</simpara></important>\r
99 </listitem>\r
100 <listitem>\r
101 <simpara>\r
102 Start all OpenSRF Perl services defined for this host:\r
103 </simpara>\r
104 <screen>osrf_ctl.sh -a start_perl</screen>\r
105 <tip><simpara>You can start an individual Perl service using:</simpara></tip>\r
106 <screen>opensrf-perl.pl -s &lt;service-name&gt; -a start -p &lt;PID-directory&gt;</screen>\r
107 </listitem>\r
108 <listitem>\r
109 <simpara>\r
110 Start all OpenSRF C services defined for this host:\r
111 </simpara>\r
112 <screen>osrf_ctl.sh -a start_c</screen>\r
113 </listitem>\r
114 </orderedlist>\r
115 </section>\r
116 <section id="_stopping_opensrf_services">\r
117 <title>Stopping OpenSRF services</title>\r
118 <simpara>Issue the following commands as the <literal>opensrf</literal> user. If you are running OpenSRF\r
119 on a single-server machine, you can use the <literal>-l</literal> flag to force the hostname\r
120 to be treated as <literal>localhost</literal>.</simpara>\r
121 <orderedlist numeration="arabic">\r
122 <listitem>\r
123 <simpara>\r
124 Stop the OpenSRF router:\r
125 </simpara>\r
126 <screen>osrf_ctl.sh -a stop_router</screen>\r
127 </listitem>\r
128 <listitem>\r
129 <simpara>\r
130 Stop all OpenSRF Perl services defined for this host:\r
131 </simpara>\r
132 <screen>osrf_ctl.sh -a stop_perl</screen>\r
133 <tip><simpara>You can stop an individual Perl service using:</simpara></tip>\r
134 <screen>opensrf-perl.pl -s &lt;service-name&gt; -a stop -p &lt;PID-directory&gt;</screen>\r
135 </listitem>\r
136 <listitem>\r
137 <simpara>\r
138 Stop all OpenSRF C services defined for this host:\r
139 </simpara>\r
140 <screen>osrf_ctl.sh -a stop_c</screen>\r
141 </listitem>\r
142 </orderedlist>\r
143 <important><simpara>PID files for OpenSRF services are stored and looked up\r
144 in <literal>/openils/var/run</literal> by default with <literal>osrf_ctl.sh</literal>, and in\r
145 <literal>/tmp/</literal> with <literal>opensrf-perl.pl</literal>.  For a clustered server instance\r
146 of Evergreen, you must store the PIDs on a directory that is local\r
147 to each server, or else one of your cluster servers may try\r
148 killing processes on itself that actually have PIDs on other servers.</simpara></important>\r
149 </section>\r
150 </section>\r
151 <section id="_examining_sample_code">\r
152 <title>Examining sample code</title>\r
153 <simpara>Show internal documentation for methods. Do some stupid srfsh tricks\r
154 (<literal>introspect</literal> for one) and show <literal>docgen.xsl</literal> in action.</simpara>\r
155 <section id="_srfsh_stupid_tricks">\r
156 <title>SRFSH stupid tricks</title>\r
157 <screen>srfsh# introspect open-ils.auth\r
158 ... returns documentation for all methods registered for open-ils.auth\r
159 \r
160 srfsh# introspect open-ils.auth "open-ils.auth.authenticate"\r
161 ... returns documentation for all methods with names beginning with\r
162     "open-ils.auth.authenticate" registered for open-ils.auth\r
163 \r
164 srfsh# open open-ils.cstore\r
165 ... begins a stateful connection with open-ils.cstore\r
166 srfsh# request open-ils.cstore open-ils.cstore.transaction.begin\r
167 ... begins a transaction\r
168 srfsh# request open-ils.cstore open-ils.cstore.direct.config.language_map.delete \\r
169    {"code": {"like":"a%"}}\r
170 ... deletes all of the entries from config.language_map that have a\r
171 ... code beginning with "e"\r
172 srfsh# request open-ils.cstore open-ils.cstore.transaction.rollback\r
173 ... rolls back the transaction\r
174 srfsh# close open-ils.cstore\r
175 ... closes the stateful connection with open-ils.cstore</screen>\r
176 </section>\r
177 <section id="_perl">\r
178 <title>Perl</title>\r
179 <section id="_services">\r
180 <title>Services</title>\r
181 <simpara>See <literal>OpenSRF/src/perl/lib/OpenSRF/UnixServer.pm</literal> to understand how the\r
182 optional methods for initializing and cleaning up OpenSRF services\r
183 are invoked:</simpara>\r
184 <itemizedlist>\r
185 <listitem>\r
186 <simpara>\r
187 <literal>initialize()</literal>\r
188 </simpara>\r
189 </listitem>\r
190 <listitem>\r
191 <simpara>\r
192 <literal>child_init()</literal>\r
193 </simpara>\r
194 </listitem>\r
195 <listitem>\r
196 <simpara>\r
197 <literal>child_exit()</literal>\r
198 </simpara>\r
199 </listitem>\r
200 </itemizedlist>\r
201 <simpara>Services are implemented as Perl functions. Each service needs to be registered with:</simpara>\r
202 <programlisting language="perl" linenumbering="unnumbered">__PACKAGE__-&gt;register_method(\r
203   method =&gt; 'method name',                      # <co id="CO1-1"/>\r
204   api_name =&gt; 'API name',                       # <co id="CO1-2"/>\r
205   api_level =&gt; 1,                               # <co id="CO1-3"/>\r
206   argc =&gt; # of args,                            # <co id="CO1-4"/>\r
207   signature =&gt; {                                # <co id="CO1-5"/>\r
208     desc =&gt; “Description”,\r
209     params =&gt; [\r
210       {\r
211         name =&gt; 'parameter name',\r
212         desc =&gt; 'parameter description',\r
213         type =&gt; '(array|hash|number|string)'\r
214       }\r
215     ],\r
216     return =&gt; {\r
217       desc =&gt; 'Description of return value',\r
218       type =&gt; '(array|hash|number|string)'\r
219     }\r
220   }\r
221 );</programlisting>\r
222 <calloutlist>\r
223 <callout arearefs="CO1-1">\r
224 <simpara>\r
225 The method name is the name of the Perl method that is called when a\r
226 client invokes the corresponding OpenSRF method.\r
227 </simpara>\r
228 </callout>\r
229 <callout arearefs="CO1-2">\r
230 <simpara>\r
231 The API name is the OpenSRF method name. By convention, each API\r
232 uses the OpenSRF service name for its root, and then appends one or more\r
233 levels of names to the OpenSRF service name, depending on the complexity\r
234 of the service and the number of methods exposed by a given service.\r
235 </simpara>\r
236 </callout>\r
237 <callout arearefs="CO1-3">\r
238 <simpara>\r
239 The API level is always <literal>1</literal>.\r
240 </simpara>\r
241 </callout>\r
242 <callout arearefs="CO1-4">\r
243 <simpara>\r
244 The number of arguments that can be passed to the OpenSRF method is\r
245 primarily for guidance purposes.\r
246 </simpara>\r
247 </callout>\r
248 <callout arearefs="CO1-5">\r
249 <simpara>\r
250 The signature is consumed by the various utilities (srfsh, docgen.xsl)\r
251 that generate documentation about the OpenSRF service.\r
252 </simpara>\r
253 </callout>\r
254 </calloutlist>\r
255 <simpara>Note that arguments are converted between native data structures and JSON\r
256 for us for free.</simpara>\r
257 </section>\r
258 <section id="_client_cheat_sheet">\r
259 <title>Client cheat sheet</title>\r
260 <simpara>This is the simplest possible OpenSRF client written in Perl:</simpara>\r
261 <programlisting language="perl" linenumbering="unnumbered"></programlisting>\r
262 <calloutlist>\r
263 <callout arearefs="">\r
264 <simpara>\r
265 The <literal>OpenSRF::System</literal> module gives our program access to the core OpenSRF\r
266 client functionality.\r
267 </simpara>\r
268 </callout>\r
269 <callout arearefs="">\r
270 <simpara>\r
271 The <literal>bootstrap_client()</literal> method reads the <literal>opensrf_core.xml</literal> file and sets\r
272 up communication with the OpenSRF router.\r
273 </simpara>\r
274 </callout>\r
275 <callout arearefs="">\r
276 <simpara>\r
277 The <literal>OpenSRF::Appsession-&gt;create()</literal> instance method asks the router if it\r
278 can connect to the named service. If the router determines that the service\r
279 is accessible (either the opensrf credentials are on the private domain, which\r
280 gives it access to all public and private services; or the service is on a\r
281 public domain, which is accessible to both public and private opensrf\r
282 credentials), it returns an OpenSRF session with a connection to the named service.\r
283 </simpara>\r
284 </callout>\r
285 <callout arearefs="">\r
286 <simpara>\r
287 The <literal>OpenSRF::Appsession-&gt;request()</literal> method invokes a method of the\r
288 associated service to return a request object.\r
289 </simpara>\r
290 </callout>\r
291 <callout arearefs="">\r
292 <simpara>\r
293 The method name that you want to invoke is the first argument to <literal>request()</literal>.\r
294 </simpara>\r
295 </callout>\r
296 <callout arearefs="">\r
297 <simpara>\r
298 The arguments to the method follow the method name.\r
299 </simpara>\r
300 </callout>\r
301 <callout arearefs="">\r
302 <simpara>\r
303 Invoking the <literal>gather()</literal> method on the returned request object returns a\r
304 single result.\r
305 </simpara>\r
306 <note><simpara>If the service is expected to return multiple results, you should loop\r
307 over it with <literal>recv()</literal> instead. But then, that wouldn&#8217;t be the simplest\r
308 possible client anymore would it?</simpara></note>\r
309 </callout>\r
310 <callout arearefs="">\r
311 <simpara>\r
312 The <literal>OpenSRF::Appsession-&gt;disconnect()</literal> instance method disconnects from\r
313 the service, enabling that child to go on and handle other requests.\r
314 </simpara>\r
315 </callout>\r
316 </calloutlist>\r
317 </section>\r
318 </section>\r
319 <section id="_javascript">\r
320 <title>JavaScript</title>\r
321 <simpara>Historically, JavaScript has had access to OpenSRF methods via the\r
322 OpenSRF HTTP gateway Apache module. You can still see this in heavy use\r
323 in the OPAC and staff client as of Evergreen 1.6, but the approach has been\r
324 deprecated as it has significant performance problems with large responses.\r
325 The successor for the OpenSRF gateway is the OpenSRF-over-HTTP translator\r
326 Apache module, which supports streaming responses for improved performance\r
327 and better support for the broad range of OpenSRF attributes.</simpara>\r
328 <section id="_invoking_methods_via_the_http_translator">\r
329 <title>Invoking methods via the HTTP Translator</title>\r
330 <simpara>The following example demonstrates the basic approach to invoking\r
331 OpenSRF methods via JavaScript. It uses just three OpenSRF JavaScript\r
332 libraries to simplify calls to the OpenSRF-over-HTTP translator,\r
333 which became available to developers as part of the OpenSRF 1.0 /\r
334 Evergreen 1.4 releases.</simpara>\r
335 <programlisting language="html" linenumbering="unnumbered"></programlisting>\r
336 <calloutlist>\r
337 <callout arearefs="">\r
338 <simpara>\r
339 opensrf.js defines most of the objects and methods required for a bare\r
340 JavaScript call to the OpenSRF HTTP translator.\r
341 </simpara>\r
342 </callout>\r
343 <callout arearefs="">\r
344 <simpara>\r
345 opensrf_xhr.js provides cross-browser XMLHttpRequest support for OpenSRF.\r
346 </simpara>\r
347 </callout>\r
348 <callout arearefs="">\r
349 <simpara>\r
350 JSON_v1.js converts the requests and responses between JavaScript and the\r
351 JSON format that the OpenSRF translator expects.\r
352 </simpara>\r
353 </callout>\r
354 <callout arearefs="">\r
355 <simpara>\r
356 Create a client session that connects to the <literal>open-ils.resolver</literal> service.\r
357 </simpara>\r
358 </callout>\r
359 <callout arearefs="">\r
360 <simpara>\r
361 Create a request object that identifies the target method and passes the\r
362 required method arguments.\r
363 </simpara>\r
364 </callout>\r
365 <callout arearefs="">\r
366 <simpara>\r
367 Define the function that will be called when the request is sent and\r
368 results are returned from the OpenSRF HTTP translator.\r
369 </simpara>\r
370 </callout>\r
371 <callout arearefs="">\r
372 <simpara>\r
373 Loop over the returned results using the <literal>recv()</literal> method.\r
374 </simpara>\r
375 </callout>\r
376 <callout arearefs="">\r
377 <simpara>\r
378 The content of each result is accessible via the content() method of\r
379 each returned result.\r
380 </simpara>\r
381 </callout>\r
382 <callout arearefs="">\r
383 <simpara>\r
384 <literal>open-ils.resolver.resolve_holdings</literal> returns a hash of values, so\r
385 invoking one of the hash keys (<literal>coverage</literal>) gives us access to that value.\r
386 </simpara>\r
387 </callout>\r
388 <callout arearefs="">\r
389 <simpara>\r
390 Actually send the request to the method; the function defined by\r
391 <literal>req.oncomplete</literal> is invoked as the results are returned.\r
392 </simpara>\r
393 </callout>\r
394 </calloutlist>\r
395 </section>\r
396 </section>\r
397 </section>\r
398 <section id="_exercise">\r
399 <title>Exercise</title>\r
400 <simpara>Build a new OpenSRF service.</simpara>\r
401 <section id="_perl_2">\r
402 <title>Perl</title>\r
403 <simpara>The challenge: implement a service that caches responses from some\r
404 other Web service (potentially cutting down on client-side latency\r
405 for something like OpenLibrary / Google Books / xISBN services, and\r
406 avoiding timeouts if the target service is not dependable). Our\r
407 example will be to build an SFX lookup service. This has the\r
408 additional advantage of enabling <literal>XmlHttpRequest</literal> from JavaScript by\r
409 hosting the services on the same domain.</simpara>\r
410 <simpara>Let&#8217;s start with the simplest possible implementation – a CGI script.</simpara>\r
411 <programlisting language="perl" linenumbering="unnumbered"></programlisting>\r
412 <simpara>Hopefully you can follow what this CGI script is doing. It works,\r
413 but it has all the disadvantages of CGI: the environment needs to\r
414 be built up on every request, and it doesn&#8217;t remember anything\r
415 from the previous times it was called, etc.</simpara>\r
416 <section id="_turning_the_cgi_script_into_an_opensrf_service">\r
417 <title>Turning the CGI script into an OpenSRF service</title>\r
418 <simpara>So now we want to turn this into an OpenSRF service.</simpara>\r
419 <orderedlist numeration="arabic">\r
420 <listitem>\r
421 <simpara>\r
422 Start by ripping out the CGI stuff, as we won&#8217;t need that any more.\r
423 </simpara>\r
424 </listitem>\r
425 <listitem>\r
426 <simpara>\r
427 To turn this into an OpenSRF service, we create a new\r
428 Perl module (<literal>OpenILS::Application::ResolverResolver</literal>). We no\r
429 longer have to convert results between Perl and JSON values, as\r
430 OpenSRF will handle that for us. We now have to register the\r
431 method with OpenSRF.\r
432 </simpara>\r
433 <programlisting language="perl" linenumbering="unnumbered"></programlisting>\r
434 </listitem>\r
435 <listitem>\r
436 <simpara>\r
437 Copy the file into the <literal>/openils/lib/perl5/OpenILS/Application/</literal> directory\r
438 so that OpenSRF can find it in the <literal>@INC</literal> search path.\r
439 </simpara>\r
440 </listitem>\r
441 <listitem>\r
442 <simpara>\r
443 Add the service to <literal>opensrf.xml</literal> so it gets started with the\r
444 other Perl services on our host of choice:\r
445 </simpara>\r
446 <programlisting language="xml" linenumbering="unnumbered">...\r
447 &lt;open-ils.resolver&gt;\r
448   &lt;keepalive&gt;3&lt;/keepalive&gt;\r
449   &lt;stateless&gt;1&lt;/stateless&gt;\r
450   &lt;language&gt;perl&lt;/language&gt;\r
451   &lt;implementation&gt;OpenILS::Application::ResolverResolver&lt;/implementation&gt;\r
452   &lt;max_requests&gt;17&lt;/max_requests&gt;\r
453   &lt;unix_config&gt;\r
454     &lt;unix_sock&gt;open-ils.resolver_unix.sock&lt;/unix_sock&gt;\r
455     &lt;unix_pid&gt;open-ils.resolver_unix.pid&lt;/unix_pid&gt;\r
456     &lt;max_requests&gt;1000&lt;/max_requests&gt;\r
457     &lt;unix_log&gt;open-ils.resolver_unix.log&lt;/unix_log&gt;\r
458     &lt;min_children&gt;5&lt;/min_children&gt;\r
459     &lt;max_children&gt;15&lt;/max_children&gt;\r
460     &lt;min_spare_children&gt;3&lt;/min_spare_children&gt;\r
461     &lt;max_spare_children&gt;5&lt;/max_spare_children&gt;\r
462   &lt;/unix_config&gt;\r
463   &lt;app_settings&gt;\r
464     &lt;cache_timeout&gt;86400&lt;/cache_timeout&gt;\r
465     &lt;default_url_base&gt;http://sfx.scholarsportal.info/laurentian&lt;/default_url_base&gt;\r
466   &lt;/app_settings&gt;\r
467 &lt;/open-ils.resolver&gt;\r
468 ...\r
469 &lt;!-- In the &lt;hosts&gt; section --&gt;\r
470 &lt;localhost&gt;\r
471   ...\r
472   &lt;appname&gt;open-ils.resolver&lt;/appname&gt;\r
473 &lt;/localhost&gt;</programlisting>\r
474 </listitem>\r
475 <listitem>\r
476 <simpara>\r
477 Add the service to <literal>opensrf_core.xml</literal> as a publicly exposed\r
478 service via the HTTP gateway and translator:\r
479 </simpara>\r
480 <programlisting language="xml" linenumbering="unnumbered">...\r
481 &lt;!-- In the public router section --&gt;\r
482 &lt;services&gt;\r
483   ...\r
484   &lt;service&gt;open-ils.resolver&lt;/service&gt;\r
485 &lt;/services&gt;\r
486 ...\r
487 &lt;!-- In the public gateway section --&gt;\r
488 &lt;services&gt;\r
489 &lt;gateway&gt;\r
490   ...\r
491   &lt;services&gt;\r
492     &lt;service&gt;open-ils.resolver&lt;/service&gt;\r
493   &lt;/services&gt;\r
494 &lt;/gateway&gt;</programlisting>\r
495 </listitem>\r
496 <listitem>\r
497 <simpara>\r
498 Restart the OpenSRF Perl services to refresh the OpenSRF\r
499 settings and start the service..\r
500 </simpara>\r
501 </listitem>\r
502 <listitem>\r
503 <simpara>\r
504 Restart Apache to enable the gateway and translator to pick up\r
505 the new service.\r
506 </simpara>\r
507 </listitem>\r
508 </orderedlist>\r
509 </section>\r
510 <section id="_add_caching">\r
511 <title>Add caching</title>\r
512 <simpara>To really make this service useful, we can take advantage of OpenSRF&#8217;s\r
513 built-in support for caching via memcached. Keeping the values\r
514 returned by the resolver for 1 week is apparently good.</simpara>\r
515 <simpara>We will also take advantage of the <literal>opensrf.settings</literal> service that\r
516 holds the values defined in the <literal>opensrf.xml</literal> configuration file to\r
517 supply some of our default arguments.</simpara>\r
518 <formalpara><title>Caching OpenSRF Resolver Service</title><para>\r
519 <programlisting language="perl" linenumbering="unnumbered"></programlisting>\r
520 </para></formalpara>\r
521 </section>\r
522 <section id="_pulling_application_settings_from_literal_opensrf_xml_literal">\r
523 <title>Pulling application settings from <literal>opensrf.xml</literal></title>\r
524 <simpara>In case you missed it in the previous diff, we also started\r
525 pulling some application-specific settings from <literal>opensrf.xml</literal>\r
526 during the <literal>initialize()</literal> phase for the service.</simpara>\r
527 <simpara>In the following diff, we enable the service to pull the default URL from\r
528 <literal>opensrf.xml</literal> rather than hard-coding it into the OpenSRF service&#8230; because\r
529 that&#8217;s just the right thing to do.</simpara>\r
530 <programlisting language="perl" linenumbering="unnumbered">=== modified file 'ResolverResolver.pm'\r
531 --- ResolverResolver.pm    2009-10-22 21:00:15 +0000\r
532 +++ ResolverResolver.pm    2009-10-24 03:00:30 +0000\r
533 @@ -77,6 +77,7 @@\r
534  my $prefix = "open-ils.resolver_"; # Prefix for caching values\r
535  my $cache;\r
536  my $cache_timeout;\r
537 +my $default_url_base;              # Default resolver location\r
538 \r
539  our ($ua, $parser);\r
540 \r
541 @@ -86,6 +87,8 @@\r
542      my $sclient = OpenSRF::Utils::SettingsClient-&gt;new();\r
543      $cache_timeout = $sclient-&gt;config_value(\r
544          "apps", "open-ils.resolver", "app_settings", "cache_timeout" ) || 300;\r
545 +    $default_url_base = $sclient-&gt;config_value(\r
546 +        "apps", "open-ils.resolver", "app_settings", "default_url_base");\r
547  }\r
548 \r
549  sub child_init {\r
550 @@ -102,14 +105,11 @@\r
551      my $conn = shift;\r
552      my $id_type = shift; # keep it simple for now, either 'issn' or 'isbn'\r
553      my $id_value = shift; # the normalized ISSN or ISBN\r
554 +    my $url_base = shift || $default_url_base;\r
555 \r
556      # We'll use this in our cache key\r
557      my $method = "open-ils.resolver.resolve_holdings";\r
558 \r
559 -    # For now we'll pass the argument with a hard-coded default\r
560 -    # Should pull these specifics from the database as part of initialize()\r
561 -    my $url_base = shift || 'http://sfx.scholarsportal.info/laurentian';\r
562 -\r
563      # Big ugly SFX OpenURL request\r
564      my $url_args = '?url_ver=Z39.88-2004&amp;url_ctx_fmt=infofi/fmt:kev:mtx:ctx&amp;'\r
565          . 'ctx_enc=UTF-8&amp;ctx_ver=Z39.88-2004&amp;rfr_id=info:sid/conifer&amp;'</programlisting>\r
566 <simpara>The <literal>opensrf.settings</literal> service caches the settings defined in <literal>opensrf.xml</literal>,\r
567 so if you change a setting in the configuration files and want that change\r
568 to take effect immediately, you have to:</simpara>\r
569 <orderedlist numeration="arabic">\r
570 <listitem>\r
571 <simpara>\r
572 Restart the <literal>opensrf.settings</literal> service to refresh the cached settings.\r
573 </simpara>\r
574 </listitem>\r
575 <listitem>\r
576 <simpara>\r
577 Restart the affected service to make the new settings take effect.\r
578 </simpara>\r
579 </listitem>\r
580 </orderedlist>\r
581 <simpara>Next step: add org_unit settings for resolver type and URL on a per-org_unit basis.\r
582 OrgUnit settings can be retrieved via\r
583 <literal>OpenILS::Application::AppUtils-&gt;ou_ancestor_setting_value($org_id, $setting_name)</literal>).</simpara>\r
584 <simpara>This is where we step beyond OpenSRF and start getting into the\r
585 Evergreen database schema (<literal>config.org_unit_setting</literal> table).</simpara>\r
586 </section>\r
587 </section>\r
588 <section id="_further_reading">\r
589 <title>Further reading</title>\r
590 <simpara>OpenSRF terminology: <ulink url="http://open-ils.org/dokuwiki/doku.php?id=osrf-devel:terms">http://open-ils.org/dokuwiki/doku.php?id=osrf-devel:terms</ulink></simpara>\r
591 </section>\r
592 </section>\r
593 <section id="_part_2_evergreen_applications">\r
594 <title>Part 2: Evergreen applications</title>\r
595 <section id="_authentication">\r
596 <title>Authentication</title>\r
597 <simpara>Although many services offer methods that can be invoked without\r
598 authentication, some methods require authentication in Evergreen.\r
599 Evergreen&#8217;s authentication framework returns an <emphasis>authentication token</emphasis>\r
600 when a user has successfully logged in to represent that user\r
601 session. You can then pass the authentication token to various\r
602 methods to ensure, for example, that the requesting user has permission\r
603 to access the circulation information attached to a particular account,\r
604 or has been granted the necessary permissions at a particular library\r
605 to perform the action that they are requesting.</simpara>\r
606 <simpara>Authentication in Evergreen is performed with the assistance of the\r
607 <literal>open-ils.auth</literal> service, which has been written in C for performance\r
608 reasons because it is invoked so frequently. A successful authentication\r
609 request requires two steps:</simpara>\r
610 <orderedlist numeration="arabic">\r
611 <listitem>\r
612 <simpara>\r
613 Retrieve an authentication seed value by invoking the\r
614 <literal>open-ils.auth.authenticate.init</literal> method, passing the user name as\r
615 the only argument. As long as the user name contains no spaces, the\r
616 method returns a seed value calculated by the MD5 checksum of\r
617 a string composed of the concatenation of the time() system call,\r
618 process ID, and user name.\r
619 </simpara>\r
620 </listitem>\r
621 <listitem>\r
622 <simpara>\r
623 Retrieve an authentication token by invoking the\r
624 <literal>open-ils.auth.authenticate.complete</literal> method, passing\r
625 a JSON hash composed of a minimum of the following arguments\r
626 (where <emphasis>seed</emphasis> represents the value returned by the\r
627 <literal>open-ils.auth.authenticate.init</literal> method):\r
628 </simpara>\r
629 <programlisting language="java" linenumbering="unnumbered">{\r
630     "username": username, // or "barcode": barcode,\r
631     "password": md5sum(seed + md5sum(password)),\r
632 }</programlisting>\r
633 </listitem>\r
634 </orderedlist>\r
635 <simpara><literal>open-ils.auth.authenticate.complete</literal> also accepts the following\r
636 additional arguments:</simpara>\r
637 <itemizedlist>\r
638 <listitem>\r
639 <simpara>\r
640 <literal>type</literal>: one of "staff" (default), "opac", or "temp"\r
641 </simpara>\r
642 </listitem>\r
643 <listitem>\r
644 <simpara>\r
645 <literal>org</literal>: the numeric ID of the org_unit where the login is active\r
646 </simpara>\r
647 </listitem>\r
648 <listitem>\r
649 <simpara>\r
650 <literal>workstation</literal>: the registered workstation name\r
651 </simpara>\r
652 </listitem>\r
653 </itemizedlist>\r
654 <section id="_authentication_in_perl">\r
655 <title>Authentication in Perl</title>\r
656 <simpara>The following example is taken directly from <literal>OpenILS::WWW::Proxy</literal>:</simpara>\r
657 <programlisting language="perl" linenumbering="unnumbered">sub oils_login {\r
658     my( $username, $password, $type ) = @_;\r
659 \r
660     $type |= "staff";\r
661     my $nametype = 'username';\r
662     $nametype = 'barcode' if ($username =~ /^\d+$/o);\r
663 \r
664     my $seed = OpenSRF::AppSession\r
665         -&gt;create("open-ils.auth")\r
666         -&gt;request( 'open-ils.auth.authenticate.init', $username )\r
667         -&gt;gather(1);\r
668 \r
669     return undef unless $seed;\r
670 \r
671     my $response = OpenSRF::AppSession\r
672         -&gt;create("open-ils.auth")\r
673         -&gt;request( 'open-ils.auth.authenticate.complete', {\r
674         $nametype =&gt; $username,\r
675             password =&gt; md5_hex($seed . md5_hex($password)),\r
676             type =&gt; $type\r
677         })\r
678         -&gt;gather(1);\r
679 \r
680     return undef unless $response;\r
681 \r
682     return $response-&gt;{payload}-&gt;{authtoken};\r
683 }</programlisting>\r
684 </section>\r
685 <section id="_authentication_in_javascript">\r
686 <title>Authentication in JavaScript</title>\r
687 <simpara>The following example provides a minimal implementation of the authentication\r
688 method in JavaScript. For a more complete implementation, you would\r
689 differentiate between user names and barcodes, potentially accept the\r
690 org_unit and workstation name for more granular permissions, and provide\r
691 exception handling.</simpara>\r
692 <programlisting language="html" linenumbering="unnumbered"></programlisting>\r
693 <calloutlist>\r
694 <callout arearefs="">\r
695 <simpara>\r
696 opensrf.js defines most of the objects and methods required for a bare\r
697 JavaScript call to the OpenSRF HTTP translator.\r
698 </simpara>\r
699 </callout>\r
700 <callout arearefs="">\r
701 <simpara>\r
702 opensrf_xhr.js provides cross-browser XMLHttpRequest support for OpenSRF.\r
703 </simpara>\r
704 </callout>\r
705 <callout arearefs="">\r
706 <simpara>\r
707 JSON_v1.js converts the requests and responses between JavaScript and the\r
708 JSON format that the OpenSRF translator expects.\r
709 </simpara>\r
710 </callout>\r
711 <callout arearefs="">\r
712 <simpara>\r
713 md5.js provides the implementation of the md5sum algorithm in the\r
714 <literal>hex_md5</literal> function\r
715 </simpara>\r
716 </callout>\r
717 <callout arearefs="">\r
718 <simpara>\r
719 Create a client session that connects to the <literal>open-ils.auth</literal> service.\r
720 </simpara>\r
721 </callout>\r
722 <callout arearefs="">\r
723 <simpara>\r
724 Create a request object that invokes the <literal>open-ils.auth.authenticate.init</literal>\r
725 method, providing the user name as the salt.\r
726 </simpara>\r
727 </callout>\r
728 <callout arearefs="">\r
729 <simpara>\r
730 Set the <literal>timeout</literal> property on the request object to make it a\r
731 synchronous call.\r
732 </simpara>\r
733 </callout>\r
734 <callout arearefs="">\r
735 <simpara>\r
736 Send the request. The method returns a seed value which is assigned to\r
737 the <literal>seed</literal> variable.\r
738 </simpara>\r
739 </callout>\r
740 <callout arearefs="">\r
741 <simpara>\r
742 Create the hash of parameters that will be sent in the request to the\r
743 <literal>open-ils.auth.authenticate.complete</literal> method, including the password and\r
744 authentication type.\r
745 </simpara>\r
746 </callout>\r
747 <callout arearefs="">\r
748 <simpara>\r
749 Assume that the credentials being sent are based on the user name rather\r
750 than the barcode. The Perl implementation tests the value of the user name\r
751 variable to determine whether it contains a digit; if it does contain a\r
752 digit, then it is considered a barcode rather than a user name. Ensure that\r
753 your implementations are consistent!\r
754 </simpara>\r
755 </callout>\r
756 <callout arearefs="">\r
757 <simpara>\r
758 Create a request object that invokes the\r
759 <literal>open-ils.auth.authenticate.complete</literal> method, passing the entire hash of\r
760 parameters. Once again, set the <literal>timeout</literal> parameter to make the request\r
761 synchronous.\r
762 </simpara>\r
763 </callout>\r
764 <callout arearefs="">\r
765 <simpara>\r
766 Assign the <literal>authtoken</literal> attribute of the returned payload to the\r
767 <literal>authtoken</literal> variable.\r
768 </simpara>\r
769 </callout>\r
770 </calloutlist>\r
771 </section>\r
772 </section>\r
773 </section>\r
774 <section id="_evergreen_data_models_and_access">\r
775 <title>Evergreen data models and access</title>\r
776 <section id="_database_schema">\r
777 <title>Database schema</title>\r
778 <simpara>The database schema is tied pretty tightly to PostgreSQL. Although PostgreSQL\r
779 adheres closely to ANSI SQL standards, the use of schemas, SQL functions\r
780 implemented in both plpgsql and plperl, and PostgreSQL&#8217;s native full-text\r
781 search would make it&#8230; challenging&#8230; to port to other database platforms.</simpara>\r
782 <simpara>A few common PostgreSQL interfaces for poking around the schema and\r
783 manipulating data are:</simpara>\r
784 <itemizedlist>\r
785 <listitem>\r
786 <simpara>\r
787 psql (the command line client)\r
788 </simpara>\r
789 </listitem>\r
790 <listitem>\r
791 <simpara>\r
792 pgadminIII (a GUI client).\r
793 </simpara>\r
794 </listitem>\r
795 </itemizedlist>\r
796 <simpara>Or you can read through the source files in Open-ILS/src/sql/Pg.</simpara>\r
797 <simpara>Let&#8217;s take a quick tour through the schemas, pointing out some highlights\r
798 and some key interdependencies:</simpara>\r
799 <itemizedlist>\r
800 <listitem>\r
801 <simpara>\r
802 actor.org_unit &#8594; asset.copy_location\r
803 </simpara>\r
804 </listitem>\r
805 <listitem>\r
806 <simpara>\r
807 actor.usr &#8594; actor.card\r
808 </simpara>\r
809 </listitem>\r
810 <listitem>\r
811 <simpara>\r
812 biblio.record_entry &#8594; asset.call_number &#8594; asset.copy\r
813 </simpara>\r
814 </listitem>\r
815 <listitem>\r
816 <simpara>\r
817 config.metabib_field &#8594; metabib.*_field_entry\r
818 </simpara>\r
819 </listitem>\r
820 </itemizedlist>\r
821 </section>\r
822 <section id="_database_access_methods">\r
823 <title>Database access methods</title>\r
824 <simpara>You could use direct access to the database via Perl DBI, JDBC, etc,\r
825 but Evergreen offers several database CRUD services for\r
826 creating / retrieving / updating / deleting data. These avoid tying\r
827 you too tightly to the current database schema and they funnel database\r
828 access through the same mechanism, rather than tying up connections\r
829 with other interfaces.</simpara>\r
830 </section>\r
831 <section id="_evergreen_interface_definition_language_idl">\r
832 <title>Evergreen Interface Definition Language (IDL)</title>\r
833 <simpara>Defines properties and required permissions for Evergreen classes.\r
834 To reduce network overhead, a given object is identified via a\r
835 class-hint and serialized as a JSON array of properties (no named properties).</simpara>\r
836 <simpara>As of 1.6, fields will be serialized in the order in which they appear\r
837 in the IDL definition file, and the is_new / is_changed / is_deleted\r
838 properties are automatically added. This has greatly reduced the size of\r
839 the <literal>fm_IDL.xml</literal> file and makes DRY people happier :)</simpara>\r
840 <itemizedlist>\r
841 <listitem>\r
842 <simpara>\r
843 &#8230; oils_persist:readonly tells us, if true, that the data lives in the database, but is pulled from the SELECT statement defined in the &lt;oils_persist:source_definition&gt; child element\r
844 </simpara>\r
845 </listitem>\r
846 </itemizedlist>\r
847 <section id="_idl_basic_example_config_language_map">\r
848 <title>IDL basic example (config.language_map)</title>\r
849 <programlisting language="xml" linenumbering="unnumbered">&lt;class id="clm" controller="open-ils.cstore open-ils.pcrud"\r
850         oils_obj:fieldmapper="config::language_map"\r
851         oils_persist:tablename="config.language_map"\r
852         reporter:label="Language Map" oils_persist:field_safe="true"&gt;      # <co id="CO5-1"/> <co id="CO5-2"/> <co id="CO5-3"/> <co id="CO5-4"/>\r
853     &lt;fields oils_persist:primary="code" oils_persist:sequence=""&gt;          # <co id="CO5-5"/>\r
854         &lt;field reporter:label="Language Code" name="code"\r
855             reporter:selector="value" reporter:datatype="text"/&gt;           # <co id="CO5-6"/>\r
856         &lt;field reporter:label="Language" name="value"\r
857             reporter:datatype="text" oils_persist:i18n="true"/&gt;            # <co id="CO5-7"/>\r
858     &lt;/fields&gt;\r
859     &lt;links/&gt;\r
860     &lt;permacrud xmlns="http://open-ils.org/spec/opensrf/IDL/permacrud/v1"&gt;  # <co id="CO5-8"/>\r
861         &lt;actions&gt;\r
862             &lt;create global_required="true" permission="CREATE_MARC_CODE"&gt;  # <co id="CO5-9"/>\r
863             &lt;retrieve global_required="true"\r
864                 permission="CREATE_MARC_CODE UPDATE_MARC_CODE DELETE_MARC_CODE"&gt;\r
865             &lt;update global_required="true" permission="UPDATE_MARC_CODE"&gt;\r
866             &lt;delete global_required="true" permission="DELETE_MARC_CODE"&gt;\r
867         &lt;/actions&gt;\r
868     &lt;/permacrud&gt;\r
869 &lt;/class&gt;</programlisting>\r
870 <calloutlist>\r
871 <callout arearefs="CO5-1">\r
872 <simpara>\r
873 The <literal>class</literal> element defines the attributes and permissions for classes,\r
874 and relationships between classes.\r
875 </simpara>\r
876 <itemizedlist>\r
877 <listitem>\r
878 <simpara>\r
879 The <literal>id</literal> attribute on the <literal>class</literal> element defines the class hint that is\r
880 used everywhere in Evergreen.\r
881 </simpara>\r
882 </listitem>\r
883 <listitem>\r
884 <simpara>\r
885 The <literal>controller</literal> attribute defines the OpenSRF\r
886 services that provide access to the data for the class objects.\r
887 </simpara>\r
888 </listitem>\r
889 </itemizedlist>\r
890 </callout>\r
891 <callout arearefs="CO5-2">\r
892 <simpara>\r
893 The <literal>oils_obj::fieldmapper</literal> attribute defines the name of the class that\r
894 is generated by <literal>OpenILS::Utils::Fieldmapper</literal>.\r
895 </simpara>\r
896 </callout>\r
897 <callout arearefs="CO5-3">\r
898 <simpara>\r
899 The <literal>oils_persist:tablename</literal> attribute defines the name of the table\r
900 that contains the data for the class objects.\r
901 </simpara>\r
902 </callout>\r
903 <callout arearefs="CO5-4">\r
904 <simpara>\r
905 The reporter interface uses <literal>reporter:label</literal> attribute values in\r
906 the source list to provide meaningful class and attribute names. The\r
907 <literal>open-ils.fielder</literal> service generates a set of methods that provide direct\r
908 access to the classes for which <literal>oils_persist:field_safe</literal> is <literal>true</literal>. For\r
909 example,\r
910 </simpara>\r
911 <screen>srfsh# request open-ils.fielder open-ils.fielder.clm.atomic \\r
912     {"query":{"code":{"=":"eng"}}}\r
913 \r
914 Received Data: [\r
915   {\r
916     "value":"English",\r
917     "code":"eng"\r
918   }\r
919 ]</screen>\r
920 </callout>\r
921 <callout arearefs="CO5-5">\r
922 <simpara>\r
923 The <literal>fields</literal> element defines the list of fields for the class.\r
924 </simpara>\r
925 <itemizedlist>\r
926 <listitem>\r
927 <simpara>\r
928 The <literal>oils_persist:primary</literal> attribute defines the column that acts as\r
929 the primary key for the table.\r
930 </simpara>\r
931 </listitem>\r
932 <listitem>\r
933 <simpara>\r
934 The <literal>oils_persist:sequence</literal> attribute holds the name of the database\r
935 sequence.\r
936 </simpara>\r
937 </listitem>\r
938 </itemizedlist>\r
939 </callout>\r
940 <callout arearefs="CO5-6">\r
941 <simpara>\r
942 Each <literal>field</literal> element defines one property of the class.\r
943 </simpara>\r
944 <itemizedlist>\r
945 <listitem>\r
946 <simpara>\r
947 The <literal>name</literal> attribute defines the getter/setter method name for the field.\r
948 </simpara>\r
949 </listitem>\r
950 <listitem>\r
951 <simpara>\r
952 The <literal>reporter:label</literal> attribute defines the attribute name as used in\r
953 the reporter interface.\r
954 </simpara>\r
955 </listitem>\r
956 <listitem>\r
957 <simpara>\r
958 The <literal>reporter:selector</literal> attribute defines the field used in the reporter\r
959 filter interface to provide a selectable list. This gives the user a more\r
960 meaningful access point than the raw numeric ID or abstract code.\r
961 </simpara>\r
962 </listitem>\r
963 <listitem>\r
964 <simpara>\r
965 The <literal>reporter:datatype</literal> attribute defines the type of data held by\r
966 this property for the purposes of the reporter.\r
967 </simpara>\r
968 </listitem>\r
969 </itemizedlist>\r
970 </callout>\r
971 <callout arearefs="CO5-7">\r
972 <simpara>\r
973 The <literal>oils_persist:i18n</literal> attribute, when <literal>true</literal>, means that\r
974 translated values for the field&#8217;s contents may be accessible in\r
975 different locales.\r
976 </simpara>\r
977 </callout>\r
978 <callout arearefs="CO5-8">\r
979 <simpara>\r
980 The <literal>permacrud</literal> element defines the permissions (if any) required\r
981 to <emphasis role="strong">c</emphasis>reate, <emphasis role="strong">r</emphasis>etrieve, <emphasis role="strong">u</emphasis>pdate, and <emphasis role="strong">d</emphasis>elete data for this\r
982 class. <literal>open-ils.permacrud</literal> must be defined as a controller for the class\r
983 for the permissions to be applied.\r
984 </simpara>\r
985 </callout>\r
986 <callout arearefs="CO5-9">\r
987 <simpara>\r
988 Each action requires one or more <literal>permission</literal> values that the\r
989 user must possess to perform the action.\r
990 </simpara>\r
991 <itemizedlist>\r
992 <listitem>\r
993 <simpara>\r
994 If the <literal>global_required</literal> attribute is <literal>true</literal>, then the user must\r
995 have been granted that permission globally (depth = 0) to perform\r
996 the action.\r
997 </simpara>\r
998 </listitem>\r
999 <listitem>\r
1000 <simpara>\r
1001 The <literal>context_field</literal> attribute denotes the <literal>&lt;field&gt;</literal> that identifies\r
1002 the org_unit at which the user must have the pertinent permission.\r
1003 </simpara>\r
1004 </listitem>\r
1005 <listitem>\r
1006 <simpara>\r
1007 An action element may contain a <literal>&lt;context_field&gt;</literal> element that\r
1008 defines the linked class (identified by the <literal>link</literal> attribute) and\r
1009 the field in the linked class that identifies the org_unit where\r
1010 the permission must be held.\r
1011 </simpara>\r
1012 <itemizedlist>\r
1013 <listitem>\r
1014 <simpara>\r
1015 If the <literal>&lt;context_field&gt;</literal> element contains a <literal>jump</literal> attribute,\r
1016 then it defines a link to a link to a class with a field identifying\r
1017 the org_unit where the permission must be held.\r
1018 </simpara>\r
1019 </listitem>\r
1020 </itemizedlist>\r
1021 </listitem>\r
1022 </itemizedlist>\r
1023 </callout>\r
1024 </calloutlist>\r
1025 </section>\r
1026 <section id="_reporter_data_types_and_their_possible_values">\r
1027 <title>Reporter data types and their possible values</title>\r
1028 <itemizedlist>\r
1029 <listitem>\r
1030 <simpara>\r
1031 <literal>bool</literal>: Boolean <literal>true</literal> or <literal>false</literal>\r
1032 </simpara>\r
1033 </listitem>\r
1034 <listitem>\r
1035 <simpara>\r
1036 <literal>id</literal>: ID of the row in the database\r
1037 </simpara>\r
1038 </listitem>\r
1039 <listitem>\r
1040 <simpara>\r
1041 <literal>int</literal>: integer value\r
1042 </simpara>\r
1043 </listitem>\r
1044 <listitem>\r
1045 <simpara>\r
1046 <literal>interval</literal>: PostgreSQL time interval\r
1047 </simpara>\r
1048 </listitem>\r
1049 \r
1050 <listitem>\r
1051 <simpara>\r
1052 <literal>link</literal>: link to another class, as defined in the <literal>&lt;links&gt;</literal>\r
1053 element of the class definition\r
1054 </simpara>\r
1055 </listitem>\r
1056 <listitem>\r
1057 <simpara>\r
1058 <literal>money</literal>: currency amount\r
1059 </simpara>\r
1060 </listitem>\r
1061 <listitem>\r
1062 <simpara>\r
1063 <literal>org_unit</literal>: list of org_units\r
1064 </simpara>\r
1065 </listitem>\r
1066 <listitem>\r
1067 <simpara>\r
1068 <literal>text</literal>: text value\r
1069 </simpara>\r
1070 </listitem>\r
1071 <listitem>\r
1072 <simpara>\r
1073 <literal>timestamp</literal>: PostgreSQL timestamp\r
1074 </simpara>\r
1075 </listitem>\r
1076 </itemizedlist>\r
1077 </section>\r
1078 <section id="_idl_example_with_linked_fields_actor_workstation">\r
1079 <title>IDL example with linked fields (actor.workstation)</title>\r
1080 <simpara>Just as tables often include columns with foreign keys that point\r
1081 to values stored in the column of a different table, IDL classes\r
1082 can contain fields that link to fields in other classes. The <literal>&lt;links&gt;</literal>\r
1083 element defines which fields link to fields in other classes, and\r
1084 the nature of the relationship:</simpara>\r
1085 <programlisting language="xml" linenumbering="unnumbered">&lt;class id="aws" controller="open-ils.cstore"\r
1086         oils_obj:fieldmapper="actor::workstation"\r
1087         oils_persist:tablename="actor.workstation"\r
1088         reporter:label="Workstation"&gt;\r
1089     &lt;fields oils_persist:primary="id"\r
1090             oils_persist:sequence="actor.workstation_id_seq"&gt;\r
1091         &lt;field reporter:label="Workstation ID" name="id"\r
1092                 reporter:datatype="id"/&gt;\r
1093         &lt;field reporter:label="Workstation Name" name="name"\r
1094                 reporter:datatype="text"/&gt;\r
1095         &lt;field reporter:label="Owning Library" name="owning_lib"\r
1096                 reporter:datatype="org_unit"/&gt;\r
1097         &lt;field reporter:label="Circulations" name="circulations"\r
1098                 oils_persist:virtual="true" reporter:datatype="link"/&gt;     # <co id="CO6-1"/>\r
1099     &lt;/fields&gt;\r
1100     &lt;links&gt;                                                                # <co id="CO6-2"/>\r
1101         &lt;link field="owning_lib" reltype="has_a" key="id"\r
1102                 map="" class="aou"/&gt;                                       # <co id="CO6-3"/>\r
1103         &lt;link field="circulations" reltype="has_many" key="workstation"\r
1104                 map="" class="circ"/&gt;\r
1105         &lt;link field="circulation_checkins" reltype="has_many"\r
1106                 key="checkin_workstation" map="" class="circ"/&gt;\r
1107     &lt;/links&gt;\r
1108 &lt;/class&gt;</programlisting>\r
1109 <calloutlist>\r
1110 <callout arearefs="CO6-1">\r
1111 <simpara>\r
1112 This field includes an <literal>oils_persist:virtual</literal> attribute with the value of\r
1113 <literal>true</literal>, meaning that the linked class <literal>circ</literal> is a virtual class.\r
1114 </simpara>\r
1115 </callout>\r
1116 <callout arearefs="CO6-2">\r
1117 <simpara>\r
1118 The <literal>&lt;links&gt;</literal> element contains 0 or more <literal>&lt;link&gt;</literal> elements.\r
1119 </simpara>\r
1120 </callout>\r
1121 <callout arearefs="CO6-3">\r
1122 <simpara>\r
1123 Each <literal>&lt;link&gt;</literal> element defines the field (<literal>field</literal>) that links to a different\r
1124 class (<literal>class</literal>), the relationship (<literal>rel_type</literal>) between this field and the target\r
1125 field (<literal>key</literal>). If the field in this class links to a virtual class, the (<literal>map</literal>)\r
1126 attribute defines the field in the target class that returns a list of matching\r
1127 objects for each object in this class.\r
1128 </simpara>\r
1129 </callout>\r
1130 </calloutlist>\r
1131 </section>\r
1132 </section>\r
1133 <section id="_literal_open_ils_cstore_literal_data_access_interfaces">\r
1134 <title><literal>open-ils.cstore</literal> data access interfaces</title>\r
1135 <simpara>For each class documented in the IDL, the <literal>open-ils.cstore</literal> service\r
1136 automatically generates a set of data access methods, based on the\r
1137 <literal>oils_persist:tablename</literal> class attribute.</simpara>\r
1138 <simpara>For example, for the class hint <literal>clm</literal>, cstore generates the following\r
1139 methods with the <literal>config.language_map</literal> qualifer:</simpara>\r
1140 <itemizedlist>\r
1141 <listitem>\r
1142 <simpara>\r
1143 <literal>open-ils.cstore.direct.config.language_map.id_list {"code" { "like": "e%" } }</literal>\r
1144 </simpara>\r
1145 <simpara>Retrieves a list composed only of the IDs that match the query.</simpara>\r
1146 </listitem>\r
1147 <listitem>\r
1148 <simpara>\r
1149 <literal>open-ils.cstore.direct.config.language_map.retrieve "eng"</literal>\r
1150 </simpara>\r
1151 <simpara>Retrieves the object that matches a specific ID.</simpara>\r
1152 </listitem>\r
1153 <listitem>\r
1154 <simpara>\r
1155 <literal>open-ils.cstore.direct.config.language_map.search {"code" : "eng"}</literal>\r
1156 </simpara>\r
1157 <simpara>Retrieves a list of objects that match the query.</simpara>\r
1158 </listitem>\r
1159 <listitem>\r
1160 <simpara>\r
1161 <literal>open-ils.cstore.direct.config.language_map.create &lt;_object_&gt;</literal>\r
1162 </simpara>\r
1163 <simpara>Creates a new object from the passed in object.</simpara>\r
1164 </listitem>\r
1165 <listitem>\r
1166 <simpara>\r
1167 <literal>open-ils.cstore.direct.config.language_map.update &lt;_object_&gt;</literal>\r
1168 </simpara>\r
1169 <simpara>Updates the object that has been passed in.</simpara>\r
1170 </listitem>\r
1171 <listitem>\r
1172 <simpara>\r
1173 <literal>open-ils.cstore.direct.config.language_map.delete "eng"</literal>\r
1174 </simpara>\r
1175 <simpara>Deletes the object that matches the query.</simpara>\r
1176 </listitem>\r
1177 </itemizedlist>\r
1178 </section>\r
1179 <section id="_open_ils_pcrud_data_access_interfaces">\r
1180 <title>open-ils.pcrud data access interfaces</title>\r
1181 <simpara>For each class documented in the IDL, the <literal>open-ils.pcrud</literal> service\r
1182 automatically generates a set of data access methods, based on the\r
1183 <literal>oils_persist:tablename</literal> class attribute.</simpara>\r
1184 <simpara>For example, for the class hint <literal>clm</literal>, <literal>open-ils.pcrud</literal> generates the following\r
1185 methods that parallel the <literal>open-ils.cstore</literal> interface:</simpara>\r
1186 <itemizedlist>\r
1187 <listitem>\r
1188 <simpara>\r
1189 <literal>open-ils.pcrud.id_list.clm &lt;_authtoken_&gt;, { "code": { "like": "e%" } }</literal>\r
1190 </simpara>\r
1191 </listitem>\r
1192 <listitem>\r
1193 <simpara>\r
1194 <literal>open-ils.pcrud.retrieve.clm &lt;_authtoken_&gt;, "eng"</literal>\r
1195 </simpara>\r
1196 </listitem>\r
1197 <listitem>\r
1198 <simpara>\r
1199 <literal>open-ils.pcrud.search.clm &lt;_authtoken_&gt;, { "code": "eng" }</literal>\r
1200 </simpara>\r
1201 </listitem>\r
1202 <listitem>\r
1203 <simpara>\r
1204 <literal>open-ils.pcrud.create.clm &lt;_authtoken_&gt;, &lt;_object_&gt;</literal>\r
1205 </simpara>\r
1206 </listitem>\r
1207 <listitem>\r
1208 <simpara>\r
1209 <literal>open-ils.pcrud.update.clm &lt;_authtoken_&gt;, &lt;_object_&gt;</literal>\r
1210 </simpara>\r
1211 </listitem>\r
1212 <listitem>\r
1213 <simpara>\r
1214 <literal>open-ils.pcrud.delete.clm &lt;_authtoken_&gt;, "eng"</literal>\r
1215 </simpara>\r
1216 </listitem>\r
1217 </itemizedlist>\r
1218 </section>\r
1219 <section id="_transaction_and_savepoint_control">\r
1220 <title>Transaction and savepoint control</title>\r
1221 <simpara>Both <literal>open-ils.cstore</literal> and <literal>open-ils.pcrud</literal> enable you to control database transactions\r
1222 to ensure that a set of operations either all succeed, or all fail,\r
1223 atomically:</simpara>\r
1224 <itemizedlist>\r
1225 <listitem>\r
1226 <simpara>\r
1227 <literal>open-ils.cstore.transaction.begin</literal>\r
1228 </simpara>\r
1229 </listitem>\r
1230 <listitem>\r
1231 <simpara>\r
1232 <literal>open-ils.cstore.transaction.commit</literal>\r
1233 </simpara>\r
1234 </listitem>\r
1235 <listitem>\r
1236 <simpara>\r
1237 <literal>open-ils.cstore.transaction.rollback</literal>\r
1238 </simpara>\r
1239 </listitem>\r
1240 <listitem>\r
1241 <simpara>\r
1242 <literal>open-ils.pcrud.transaction.begin</literal>\r
1243 </simpara>\r
1244 </listitem>\r
1245 <listitem>\r
1246 <simpara>\r
1247 <literal>open-ils.pcrud.transaction.commit</literal>\r
1248 </simpara>\r
1249 </listitem>\r
1250 <listitem>\r
1251 <simpara>\r
1252 <literal>open-ils.pcrud.transaction.rollback</literal>\r
1253 </simpara>\r
1254 </listitem>\r
1255 </itemizedlist>\r
1256 <simpara>At a more granular level, <literal>open-ils.cstore</literal> and <literal>open-ils.pcrud</literal> enable you to set database\r
1257 savepoints to ensure that a set of operations either all succeed, or all\r
1258 fail, atomically, within a given transaction:</simpara>\r
1259 <itemizedlist>\r
1260 <listitem>\r
1261 <simpara>\r
1262 <literal>open-ils.cstore.savepoint.begin</literal>\r
1263 </simpara>\r
1264 </listitem>\r
1265 <listitem>\r
1266 <simpara>\r
1267 <literal>open-ils.cstore.savepoint.commit</literal>\r
1268 </simpara>\r
1269 </listitem>\r
1270 <listitem>\r
1271 <simpara>\r
1272 <literal>open-ils.cstore.savepoint.rollback</literal>\r
1273 </simpara>\r
1274 </listitem>\r
1275 <listitem>\r
1276 <simpara>\r
1277 <literal>open-ils.pcrud.savepoint.begin</literal>\r
1278 </simpara>\r
1279 </listitem>\r
1280 <listitem>\r
1281 <simpara>\r
1282 <literal>open-ils.pcrud.savepoint.commit</literal>\r
1283 </simpara>\r
1284 </listitem>\r
1285 <listitem>\r
1286 <simpara>\r
1287 <literal>open-ils.pcrud.savepoint.rollback</literal>\r
1288 </simpara>\r
1289 </listitem>\r
1290 </itemizedlist>\r
1291 <simpara>Transactions and savepoints must be performed within a stateful\r
1292 connection to the <literal>open-ils.cstore</literal> and <literal>open-ils.pcrud</literal> services.\r
1293 In <literal>srfsh</literal>, you can open a stateful connection using the <literal>open</literal>\r
1294 command, and then close the stateful connection using the <literal>close</literal>\r
1295 command - for example:</simpara>\r
1296 <screen>srfsh# open open-ils.cstore\r
1297 ... perform various transaction-related work\r
1298 srfsh# close open-ils.cstore</screen>\r
1299 <section id="_json_queries">\r
1300 <title>JSON Queries</title>\r
1301 <simpara>Beyond simply retrieving objects by their ID using the <literal>\*.retrieve</literal>\r
1302 methods, you can issue queries against the <literal>\*.delete</literal> and <literal>\*.search</literal>\r
1303 methods using JSON to filter results with simple or complex search\r
1304 conditions.</simpara>\r
1305 <simpara>For example, to generate a list of barcodes that are held in a\r
1306 copy location that allows holds and is visible in the OPAC:</simpara>\r
1307 <programlisting language="sh" linenumbering="unnumbered">srfsh# request open-ils.cstore open-ils.cstore.json_query      #\ <co id="CO7-1"/>\r
1308     {"select": {"acp":["barcode"], "acpl":["name"]},           #\ <co id="CO7-2"/>\r
1309      "from":   {"acp":"acpl"},                                 #\ <co id="CO7-3"/>\r
1310      "where":  [                                               #\ <co id="CO7-4"/>\r
1311          {"+acpl": "holdable"},                                #\ <co id="CO7-5"/>\r
1312          {"+acpl": "opac_visible"}                             #\ <co id="CO7-6"/>\r
1313      ]}\r
1314 \r
1315 Received Data: {\r
1316   "barcode":"BARCODE1",\r
1317   "name":"Stacks"\r
1318 }\r
1319 \r
1320 Received Data: {\r
1321   "barcode":"BARCODE2",\r
1322   "name":"Stacks"\r
1323 }</programlisting>\r
1324 <calloutlist>\r
1325 <callout arearefs="CO7-1">\r
1326 <simpara>\r
1327 Invoke the <literal>json_query</literal> service.\r
1328 </simpara>\r
1329 </callout>\r
1330 <callout arearefs="CO7-2">\r
1331 <simpara>\r
1332 Select the <literal>barcode</literal> field from the <literal>acp</literal> class and the <literal>name</literal>\r
1333 field from the <literal>acpl</literal> class.\r
1334 </simpara>\r
1335 </callout>\r
1336 <callout arearefs="CO7-3">\r
1337 <simpara>\r
1338 Join the <literal>acp</literal> class to the <literal>acpl</literal> class based on the linked field\r
1339 defined in the IDL.\r
1340 </simpara>\r
1341 </callout>\r
1342 <callout arearefs="CO7-4">\r
1343 <simpara>\r
1344 Add a <literal>where</literal> clause to filter the results. We have more than one\r
1345 condition beginning with the same key, so we wrap the conditions inside\r
1346 an array.\r
1347 </simpara>\r
1348 </callout>\r
1349 <callout arearefs="CO7-5">\r
1350 <simpara>\r
1351 The first condition tests whether the boolean value of the <literal>holdable</literal>\r
1352 field on the <literal>acpl</literal> class is true.\r
1353 </simpara>\r
1354 </callout>\r
1355 <callout arearefs="CO7-6">\r
1356 <simpara>\r
1357 The second condition tests whether the boolean value of the\r
1358 <literal>opac_visible</literal> field on the <literal>acpl</literal> class is true.\r
1359 </simpara>\r
1360 </callout>\r
1361 </calloutlist>\r
1362 <simpara>For thorough coverage of the breadth of support offered by JSON\r
1363 query syntax, see <ulink url="http://open-ils.org/dokuwiki/doku.php?id=documentation:technical:jsontutorial">JSON Queries: A Tutorial</ulink>.</simpara>\r
1364 </section>\r
1365 <section id="_fleshing_linked_objects">\r
1366 <title>Fleshing linked objects</title>\r
1367 <simpara>A simplistic approach to retrieving a set of objects that are linked to\r
1368 an object that you are retrieving - for example, a set of call numbers\r
1369 linked to the barcodes that a given user has borrowed - would be to:\r
1370   1. Retrieve the list of circulation objects (<literal>circ</literal> class)\r
1371 for a given user (<literal>usr</literal> class).\r
1372   2. For each circulation object, look up the target copy (<literal>target_copy</literal>\r
1373 field, linked to the <literal>acp</literal> class).\r
1374   3. For each copy, look up the call number for that copy (<literal>call_number</literal>\r
1375 field, linked to the <literal>acn</literal> class).</simpara>\r
1376 <simpara>However, this would result in potentially hundreds of round-trip\r
1377 queries from the client to the server. Even with low-latency connections,\r
1378 the network overhead would be considerable. So, built into the <literal>open-ils.cstore</literal> and\r
1379 <literal>open-ils.pcrud</literal> access methods is the ability to <emphasis>flesh</emphasis> linked fields -\r
1380 that is, rather than return an identifier to a given linked field,\r
1381 the method can return the entire object as part of the initial response.</simpara>\r
1382 <simpara>Most of the interfaces that return class instances from the IDL offer the\r
1383 ability to flesh returned fields. For example, the\r
1384 <literal>open-ils.cstore.direct.\*.retrieve</literal> methods allow you to specify a\r
1385 JSON structure defining the fields you wish to flesh in the returned object.</simpara>\r
1386 <formalpara><title>Fleshing fields in objects returned by <literal>open-ils.cstore</literal></title><para>\r
1387 <programlisting language="sh" linenumbering="unnumbered">srfsh# request open-ils.cstore open-ils.cstore.direct.asset.copy.retrieve 1, \\r
1388     {\r
1389         "flesh": 1,                                                       #\ <co id="CO8-1"/>\r
1390         "flesh_fields": {                                                 #\ <co id="CO8-2"/>\r
1391             "acp": ["location"]\r
1392         }\r
1393     }</programlisting>\r
1394 </para></formalpara>\r
1395 <calloutlist>\r
1396 <callout arearefs="CO8-1">\r
1397 <simpara>\r
1398 The <literal>flesh</literal> argument is the depth at which objects should be fleshed.\r
1399 For example, to flesh out a field that links to another object that includes\r
1400 a field that links to another object, you would specify a depth of 2.\r
1401 </simpara>\r
1402 </callout>\r
1403 <callout arearefs="CO8-2">\r
1404 <simpara>\r
1405 The <literal>flesh_fields</literal> argument contains a list of objects with the fields\r
1406 to flesh for each object.\r
1407 </simpara>\r
1408 </callout>\r
1409 </calloutlist>\r
1410 <simpara>Let&#8217;s flesh things a little deeper. In addition to the copy location,\r
1411 let&#8217;s also flesh the call number attached to the copy, and then flesh\r
1412 the bibliographic record attached to the call number.</simpara>\r
1413 <formalpara><title>Fleshing fields in fields of objects returned by <literal>open-ils.cstore</literal></title><para>\r
1414 <programlisting language="java" linenumbering="unnumbered">request open-ils.cstore open-ils.cstore.direct.asset.copy.retrieve 1, \\r
1415     {\r
1416         "flesh": 2,\r
1417         "flesh_fields": {\r
1418             "acp": ["location", "call_number"],\r
1419             "acn": ["record"]\r
1420          }\r
1421     }</programlisting>\r
1422 </para></formalpara>\r
1423 </section>\r
1424 </section>\r
1425 <section id="_adding_an_idl_entry_for_resolverresolver">\r
1426 <title>Adding an IDL entry for ResolverResolver</title>\r
1427 <simpara>Most OpenSRF methods in Evergreen define their object interface in the\r
1428 IDL. Without an entry in the IDL, the prospective caller of a given\r
1429 method is forced to either call the method and inspect the returned\r
1430 contents, or read the source to work out the structure of the JSON\r
1431 payload. At this stage of the tutorial, we have not defined an entry\r
1432 in the IDL to represent the object returned by the\r
1433 <literal>open-ils.resolver.resolve_holdings</literal> method. It is time to complete\r
1434 that task.</simpara>\r
1435 <simpara>The <literal>open-ils.resolver</literal> service is unlike many of the other classes\r
1436 defined in the IDL because its data is not stored in the Evergreen\r
1437 database. Instead, the data is requested from an external Web service\r
1438 and only temporarily cached in <literal>memcached</literal>. Fortunately, the IDL\r
1439 enables us to represent this kind of class by setting the\r
1440 <literal>oils_persist:virtual</literal> class attribute to <literal>true</literal>.</simpara>\r
1441 <simpara>So, let&#8217;s add an entry to the IDL for the <literal>open-ils.resolver.resolve_holdings</literal>\r
1442 service:</simpara>\r
1443 <programlisting language="xml" linenumbering="unnumbered"></programlisting>\r
1444 <simpara>And let&#8217;s make <literal>ResolverResolver.pm</literal> return an array composed of our new\r
1445 <literal>rhr</literal> classes rather than raw JSON objects:</simpara>\r
1446 <programlisting language="perl" linenumbering="unnumbered"></programlisting>\r
1447 <simpara>Once we add the new entry to the IDL and copy the revised <literal>ResolverResolver.pm</literal>\r
1448 Perl module to <literal>/openils/lib/perl5/OpenILS/Application/</literal>, we need to:</simpara>\r
1449 <orderedlist numeration="arabic">\r
1450 <listitem>\r
1451 <simpara>\r
1452 Copy the updated IDL to both the <literal>/openils/conf/</literal> and\r
1453 <literal>/openils/var/web/reports/</literal> directories. The Dojo approach to\r
1454 parsing the IDL uses the IDL stored in the reports directory.\r
1455 </simpara>\r
1456 </listitem>\r
1457 <listitem>\r
1458 <simpara>\r
1459 Restart the Perl services to make the new IDL visible to the services\r
1460 and refresh the <literal>open-ils.resolver</literal> implementation\r
1461 </simpara>\r
1462 </listitem>\r
1463 <listitem>\r
1464 <simpara>\r
1465 Rerun <literal>/openils/bin/autogen.sh</literal> to regenerate the JavaScript versions\r
1466 of the IDL required by the HTTP translator and gateway.\r
1467 </simpara>\r
1468 </listitem>\r
1469 </orderedlist>\r
1470 <simpara>We also need to adjust our JavaScript client to use the nifty new\r
1471 objects that <literal>open-ils.resolver.resolve_holdings</literal> now returns.\r
1472 The best approach is to use the support in Evergreen&#8217;s Dojo extensions\r
1473 to generate the JavaScript classes directly from the IDL XML file.</simpara>\r
1474 <formalpara><title>Accessing classes defined in the IDL via Fieldmapper</title><para>\r
1475 <programlisting language="html" linenumbering="unnumbered"></programlisting>\r
1476 </para></formalpara>\r
1477 <calloutlist>\r
1478 <callout arearefs="">\r
1479 <simpara>\r
1480 Load the Dojo core.\r
1481 </simpara>\r
1482 </callout>\r
1483 <callout arearefs="">\r
1484 <simpara>\r
1485 <literal>fieldmapper.AutoIDL</literal> reads <literal>/openils/var/reports/fm_IDL.xml</literal> to\r
1486 generate a list of class properties.\r
1487 </simpara>\r
1488 </callout>\r
1489 <callout arearefs="">\r
1490 <simpara>\r
1491 <literal>fieldmapper.dojoData</literal> seems to provide a store for Evergreen data\r
1492 accessed via Dojo.\r
1493 </simpara>\r
1494 </callout>\r
1495 <callout arearefs="">\r
1496 <simpara>\r
1497 <literal>fieldmapper.Fieldmapper</literal> converts the list of class properties into\r
1498 actual classes.\r
1499 </simpara>\r
1500 </callout>\r
1501 <callout arearefs="">\r
1502 <simpara>\r
1503 <literal>fieldmapper.standardRequest</literal> invokes an OpenSRF method and returns\r
1504 an array of objects.\r
1505 </simpara>\r
1506 </callout>\r
1507 <callout arearefs="">\r
1508 <simpara>\r
1509 The first argument to <literal>fieldmapper.standardRequest</literal> is an array\r
1510 containing the OpenSRF service name and method name.\r
1511 </simpara>\r
1512 </callout>\r
1513 <callout arearefs="">\r
1514 <simpara>\r
1515 The second argument to <literal>fieldmapper.standardRequest</literal> is an array\r
1516 containing the arguments to pass to the OpenSRF method.\r
1517 </simpara>\r
1518 </callout>\r
1519 <callout arearefs="">\r
1520 <simpara>\r
1521 As Fieldmapper has instantiated the returned objects based on their\r
1522 class hints, we can invoke getter/setter methods on the objects.\r
1523 </simpara>\r
1524 </callout>\r
1525 </calloutlist>\r
1526 </section>\r
1527 </section>\r
1528 <section id="_license">\r
1529 <title>License</title>\r
1530 <simpara>This work is licensed under a <ulink url="http://creativecommons.org/licenses/by-sa/2.5/ca/">Creative Commons Attribution-Share Alike 2.5 Canada License</ulink>.</simpara>\r
1531 </section>\r
1532 </chapter>\r