From 89d73e9047c7f0fae53bfe4a31223d9b25f77b39 Mon Sep 17 00:00:00 2001 From: dbs Date: Mon, 8 Nov 2010 21:21:31 +0000 Subject: [PATCH] Add the OpenURL resolver caching service as an optional component Currently limited to supporting the Ex Libris SFX OpenURL resolver, this caches the results of looking up full-text availability of resources by ISBN or ISSN. For an example of incorporating this service into your OPAC, see http://svn.open-ils.org/trac/ILS-Contrib/browser/conifer/branches/rel_1_6_1/web/opac/skin/lul/xml/result/result_table.xml#L102 for returning the full-text availability of a given resource by ISSN. git-svn-id: svn://svn.open-ils.org/ILS/trunk@18666 dcc99617-32d9-48b4-a31d-7c20da2025e4 --- Open-ILS/examples/fm_IDL.xml | 8 + Open-ILS/examples/opensrf.xml.example | 25 ++ Open-ILS/examples/opensrf_core.xml.example | 1 + .../OpenILS/Application/ResolverResolver.pm | 304 +++++++++++++++++++++ 4 files changed, 338 insertions(+) create mode 100644 Open-ILS/src/perlmods/OpenILS/Application/ResolverResolver.pm diff --git a/Open-ILS/examples/fm_IDL.xml b/Open-ILS/examples/fm_IDL.xml index 3ef188be7e..515d4ca7fc 100644 --- a/Open-ILS/examples/fm_IDL.xml +++ b/Open-ILS/examples/fm_IDL.xml @@ -128,6 +128,14 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + + + + + + + + diff --git a/Open-ILS/examples/opensrf.xml.example b/Open-ILS/examples/opensrf.xml.example index acbd585dd5..f7576aa30e 100644 --- a/Open-ILS/examples/opensrf.xml.example +++ b/Open-ILS/examples/opensrf.xml.example @@ -1007,6 +1007,30 @@ vim:et:ts=4:sw=4: + + 3 1 @@ -1125,6 +1149,7 @@ vim:et:ts=4:sw=4: open-ils.ingest open-ils.reporter open-ils.reporter-store + open-ils.resolver open-ils.permacrud open-ils.pcrud open-ils.trigger diff --git a/Open-ILS/examples/opensrf_core.xml.example b/Open-ILS/examples/opensrf_core.xml.example index 0d803fc9a8..cebffeee44 100644 --- a/Open-ILS/examples/opensrf_core.xml.example +++ b/Open-ILS/examples/opensrf_core.xml.example @@ -30,6 +30,7 @@ Example OpenSRF bootstrap configuration file for Evergreen open-ils.pcrud open-ils.permacrud open-ils.reporter + open-ils.resolver open-ils.search open-ils.supercat open-ils.vandelay diff --git a/Open-ILS/src/perlmods/OpenILS/Application/ResolverResolver.pm b/Open-ILS/src/perlmods/OpenILS/Application/ResolverResolver.pm new file mode 100644 index 0000000000..3463c1e9ef --- /dev/null +++ b/Open-ILS/src/perlmods/OpenILS/Application/ResolverResolver.pm @@ -0,0 +1,304 @@ +#!/usr/bin/perl + +# Copyright (C) 2009-2010 Dan Scott + +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +=head1 NAME + +OpenILS::Application::ResolverResolver - retrieves holdings from OpenURL resolvers + +=head1 SYNOPSIS + +Via srfsh: + request open-ils.resolver open-ils.resolver.resolve_holdings "issn", "0022-362X" +or: + request open-ils.resolver open-ils.resolver.resolve_holdings.raw "issn", "0022-362X" + +Via Perl: + my $session = OpenSRF::AppSession->create("open-ils.resolver"); + my $request = $session->request("open-ils.resolver.resolve_holdings", [ "issn", "0022-362X" ] )->gather(); + $session->disconnect(); + + # $request is a reference to the list of hashes + +=head1 DESCRIPTION + +OpenILS::Application::ResolverResolver caches responses from OpenURL resolvers +to requests for full-text holdings. Currently integration with SFX is supported. + +Each org_unit can specify a different base URL as the third argument to +resolve_holdings(). Eventually org_units will have org_unit settings to hold +their resolver type and base URL. + +=head1 AUTHOR + +Dan Scott, dscott@laurentian.ca + +=cut + +package OpenILS::Application::ResolverResolver; + +use strict; +use warnings; +use LWP::UserAgent; +use XML::LibXML; + +# All OpenSRF applications must be based on OpenSRF::Application or +# a subclass thereof. Makes sense, eh? +use OpenILS::Application; +use base qw/OpenILS::Application/; + +# This is the client class, used for connecting to open-ils.storage +use OpenSRF::AppSession; + +# This is an extension of Error.pm that supplies some error types to throw +use OpenSRF::EX qw(:try); + +# This is a helper class for querying the OpenSRF Settings application ... +use OpenSRF::Utils::SettingsClient; + +# ... and here we have the built in logging helper ... +use OpenSRF::Utils::Logger qw($logger); + +# ... and this manages cached results for us ... +use OpenSRF::Utils::Cache; + +# ... and this gives us access to the Fieldmapper +use OpenILS::Utils::Fieldmapper; + +my $prefix = "open-ils.resolver_"; # Prefix for caching values +my $cache; +my $cache_timeout; +my $default_url_base; # Default resolver location + +our ($ua, $parser); + + +sub initialize { + $cache = OpenSRF::Utils::Cache->new('global'); + my $sclient = OpenSRF::Utils::SettingsClient->new(); + $cache_timeout = $sclient->config_value( + "apps", "open-ils.resolver", "app_settings", "cache_timeout" ) || 300; + $default_url_base = $sclient->config_value( + "apps", "open-ils.resolver", "app_settings", "default_url_base"); +} + +sub child_init { + # We need a User Agent to speak to the SFX beast + $ua = new LWP::UserAgent; + $ua->agent('SameOrigin/1.0'); + + # SFX returns XML to us; let us parse + $parser = new XML::LibXML; +} + +sub resolve_holdings { + my $self = shift; + my $conn = shift; + my $id_type = shift; # keep it simple for now, either 'issn' or 'isbn' + my $id_value = shift; # the normalized ISSN or ISBN + my $url_base = shift || $default_url_base; + + # We'll use this in our cache key + my $method = $self->api_name; + + # We might want to return raw JSON for speedier responses + my $format = 'fieldmapper'; + if ($self->api_name =~ /raw$/) { + $format = 'raw'; + } + + # Big ugly SFX OpenURL request + my $url_args = '?url_ver=Z39.88-2004&url_ctx_fmt=infofi/fmt:kev:mtx:ctx&' + . 'ctx_enc=UTF-8&ctx_ver=Z39.88-2004&rfr_id=info:sid/conifer&' + . 'sfx.ignore_date_threshold=1&' + . 'sfx.response_type=multi_obj_detailed_xml&__service_type=getFullTxt'; + + if ($id_type eq 'issn') { + $url_args .= "&rft.issn=$id_value"; + } elsif ($id_type eq 'isbn') { + $url_args .= "&rft.isbn=$id_value"; + } + + my $ckey = $prefix . $method . $url_base . $id_type . $id_value; + + # Check the cache to see if we've already looked this up + # If we have, shortcut our return value + my $result = $cache->get_cache($ckey) || undef; + if ($result) { + $logger->info("Resolver found a cache hit"); + return $result; + } + + # Otherwise, let's go and grab the info from the SFX server + my $req = HTTP::Request->new('GET', "$url_base$url_args"); + + # Let's see what we we're trying to request + $logger->info("Resolving the following request: $url_base$url_args"); + + my $res = $ua->request($req); + + my $xml = $res->content; + my $parsed_sfx = $parser->parse_string($xml); + + my (@targets) = $parsed_sfx->findnodes('//target'); + + my @sfx_result; + foreach my $target (@targets) { + if ($format eq 'raw') { + push @sfx_result, { + public_name => $target->findvalue('./target_public_name'), + target_url => $target->findvalue('.//target_url'), + target_coverage => $target->findvalue('.//coverage_statement'), + target_embargo => $target->findvalue('.//embargo_statement'), + }; + } else { + my $rhr = Fieldmapper::resolver::holdings_record->new; + $rhr->public_name($target->findvalue('./target_public_name')); + $rhr->target_url($target->findvalue('.//target_url')); + $rhr->target_coverage($target->findvalue('.//coverage_statement')); + $rhr->target_embargo($target->findvalue('.//embargo_statement')); + push @sfx_result, $rhr; + } + } + + # Stuff this into the cache + $cache->put_cache($ckey, \@sfx_result, $cache_timeout); + + # Don't return the list unless it contains results + if (scalar(@sfx_result)) { + return \@sfx_result; + } + + return undef; +} + +__PACKAGE__->register_method( + method => 'resolve_holdings', + api_name => 'open-ils.resolver.resolve_holdings', + api_level => 1, + argc => 3, + signature => { + desc => <<" DESC", +Returns a list of "rhr" objects representing the full-text holdings for a given ISBN or ISSN + DESC + 'params' => [ { + name => 'id_type', + desc => 'The type of identifier ("issn" or "isbn")', + type => 'string' + }, { + name => 'id_value', + desc => 'The identifier value', + type => 'string' + }, { + name => 'url_base', + desc => 'The base URL for the resolver and instance', + type => 'string' + }, + ], + 'return' => { + desc => 'Returns a list of "rhr" objects representing the full-text holdings for a given ISBN or ISSN', + type => 'array' + } + } +); + +__PACKAGE__->register_method( + method => 'resolve_holdings', + api_name => 'open-ils.resolver.resolve_holdings.raw', + api_level => 1, + argc => 3, + signature => { + desc => <<" DESC", +Returns a list of raw JSON objects representing the full-text holdings for a given ISBN or ISSN + DESC + 'params' => [ { + name => 'id_type', + desc => 'The type of identifier ("issn" or "isbn")', + type => 'string' + }, { + name => 'id_value', + desc => 'The identifier value', + type => 'string' + }, { + name => 'url_base', + desc => 'The base URL for the resolver and instance', + type => 'string' + }, + ], + 'return' => { + desc => 'Returns a list of raw JSON objects representing the full-text holdings for a given ISBN or ISSN', + type => 'array' + } + } +); + +# Clear cache for specific lookups +sub delete_cached_holdings { + my $self = shift; + my $conn = shift; + my $id_type = shift; # keep it simple for now, either 'issn' or 'isbn' + my $id_value = shift; # the normalized ISSN or ISBN + my $url_base = shift || $default_url_base; + my @deleted_keys; + + $logger->warn("Deleting value [$id_value]"); + # We'll use this in our cache key + foreach my $method ('open-ils.resolver.resolve_holdings.raw', 'open-ils.resolver.resolve_holdings') { + my $ckey = $prefix . $method . $url_base . $id_type . $id_value; + + $logger->warn("Deleted cache key [$ckey]"); + my $result = $cache->delete_cache($ckey); + + $logger->warn("Result of deleting cache key: [$result]"); + push @deleted_keys, $result; + } + + return \@deleted_keys; +} + +__PACKAGE__->register_method( + method => 'delete_holdings_cache', + api_name => 'open-ils.resolver.delete_cached_holdings', + api_level => 1, + argc => 3, + signature => { + desc => <<" DESC", +Deletes the cached value of the full-text holdings for a given ISBN or ISSN + DESC + 'params' => [ { + name => 'id_type', + desc => 'The type of identifier ("issn" or "isbn")', + type => 'string' + }, { + name => 'id_value', + desc => 'The identifier value', + type => 'string' + }, { + name => 'url_base', + desc => 'The base URL for the resolver and instance', + type => 'string' + }, + ], + 'return' => { + desc => 'Deletes the cached value of the full-text holdings for a given ISBN or ISSN', + type => 'array' + } + } +); + + +1; -- 2.11.0