3 # Copyright (C) 2009-2010 Dan Scott <dscott@laurentian.ca>
5 # This program is free software; you can redistribute it and/or
6 # modify it under the terms of the GNU General Public License
7 # as published by the Free Software Foundation; either version 2
8 # of the License, or (at your option) any later version.
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
15 # You should have received a copy of the GNU General Public License
16 # along with this program; if not, write to the Free Software
17 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
21 OpenILS::Application::ResolverResolver - retrieves holdings from OpenURL resolvers
26 request open-ils.resolver open-ils.resolver.resolve_holdings "issn", "0022-362X"
28 request open-ils.resolver open-ils.resolver.resolve_holdings.raw "issn", "0022-362X"
31 my $session = OpenSRF::AppSession->create("open-ils.resolver");
32 my $request = $session->request("open-ils.resolver.resolve_holdings", [ "issn", "0022-362X" ] )->gather();
33 $session->disconnect();
35 # $request is a reference to the list of hashes
39 OpenILS::Application::ResolverResolver caches responses from OpenURL resolvers
40 to requests for full-text holdings. Currently integration with SFX is supported.
42 Each org_unit can specify a different base URL as the third argument to
43 resolve_holdings(). Eventually org_units will have org_unit settings to hold
44 their resolver type and base URL.
48 Dan Scott, dscott@laurentian.ca
52 package OpenILS::Application::ResolverResolver;
59 # All OpenSRF applications must be based on OpenSRF::Application or
60 # a subclass thereof. Makes sense, eh?
61 use OpenILS::Application;
62 use base qw/OpenILS::Application/;
64 # This is the client class, used for connecting to open-ils.storage
65 use OpenSRF::AppSession;
67 # This is an extension of Error.pm that supplies some error types to throw
68 use OpenSRF::EX qw(:try);
70 # This is a helper class for querying the OpenSRF Settings application ...
71 use OpenSRF::Utils::SettingsClient;
73 # ... and here we have the built in logging helper ...
74 use OpenSRF::Utils::Logger qw($logger);
76 # ... and this manages cached results for us ...
77 use OpenSRF::Utils::Cache;
79 # ... and this gives us access to the Fieldmapper
80 use OpenILS::Utils::Fieldmapper;
82 my $prefix = "open-ils.resolver_"; # Prefix for caching values
85 my $default_url_base; # Default resolver location
91 $cache = OpenSRF::Utils::Cache->new('global');
92 my $sclient = OpenSRF::Utils::SettingsClient->new();
93 $cache_timeout = $sclient->config_value(
94 "apps", "open-ils.resolver", "app_settings", "cache_timeout" ) || 300;
95 $default_url_base = $sclient->config_value(
96 "apps", "open-ils.resolver", "app_settings", "default_url_base");
100 # We need a User Agent to speak to the SFX beast
101 $ua = new LWP::UserAgent;
102 $ua->agent('SameOrigin/1.0');
104 # SFX returns XML to us; let us parse
105 $parser = new XML::LibXML;
108 sub resolve_holdings {
111 my $id_type = shift; # keep it simple for now, either 'issn' or 'isbn'
112 my $id_value = shift; # the normalized ISSN or ISBN
113 my $url_base = shift || $default_url_base;
115 # We'll use this in our cache key
116 my $method = $self->api_name;
118 # We might want to return raw JSON for speedier responses
119 my $format = 'fieldmapper';
120 if ($self->api_name =~ /raw$/) {
124 # Big ugly SFX OpenURL request
125 my $url_args = '?url_ver=Z39.88-2004&url_ctx_fmt=infofi/fmt:kev:mtx:ctx&'
126 . 'ctx_enc=UTF-8&ctx_ver=Z39.88-2004&rfr_id=info:sid/conifer&'
127 . 'sfx.ignore_date_threshold=1&'
128 . 'sfx.response_type=multi_obj_detailed_xml&__service_type=getFullTxt';
130 if ($id_type eq 'issn') {
131 $url_args .= "&rft.issn=$id_value";
132 } elsif ($id_type eq 'isbn') {
133 $url_args .= "&rft.isbn=$id_value";
136 my $ckey = $prefix . $method . $url_base . $id_type . $id_value;
138 # Check the cache to see if we've already looked this up
139 # If we have, shortcut our return value
140 my $result = $cache->get_cache($ckey) || undef;
142 $logger->info("Resolver found a cache hit");
146 # Otherwise, let's go and grab the info from the SFX server
147 my $req = HTTP::Request->new('GET', "$url_base$url_args");
149 # Let's see what we we're trying to request
150 $logger->info("Resolving the following request: $url_base$url_args");
152 my $res = $ua->request($req);
154 my $xml = $res->content;
155 my $parsed_sfx = $parser->parse_string($xml);
157 my (@targets) = $parsed_sfx->findnodes('//target');
160 foreach my $target (@targets) {
161 if ($format eq 'raw') {
163 public_name => $target->findvalue('./target_public_name'),
164 target_url => $target->findvalue('.//target_url'),
165 target_coverage => $target->findvalue('.//coverage_statement'),
166 target_embargo => $target->findvalue('.//embargo_statement'),
169 my $rhr = Fieldmapper::resolver::holdings_record->new;
170 $rhr->public_name($target->findvalue('./target_public_name'));
171 $rhr->target_url($target->findvalue('.//target_url'));
172 $rhr->target_coverage($target->findvalue('.//coverage_statement'));
173 $rhr->target_embargo($target->findvalue('.//embargo_statement'));
174 push @sfx_result, $rhr;
178 # Stuff this into the cache
179 $cache->put_cache($ckey, \@sfx_result, $cache_timeout);
181 # Don't return the list unless it contains results
182 if (scalar(@sfx_result)) {
189 __PACKAGE__->register_method(
190 method => 'resolve_holdings',
191 api_name => 'open-ils.resolver.resolve_holdings',
196 Returns a list of "rhr" objects representing the full-text holdings for a given ISBN or ISSN
200 desc => 'The type of identifier ("issn" or "isbn")',
204 desc => 'The identifier value',
208 desc => 'The base URL for the resolver and instance',
213 desc => 'Returns a list of "rhr" objects representing the full-text holdings for a given ISBN or ISSN',
219 __PACKAGE__->register_method(
220 method => 'resolve_holdings',
221 api_name => 'open-ils.resolver.resolve_holdings.raw',
226 Returns a list of raw JSON objects representing the full-text holdings for a given ISBN or ISSN
230 desc => 'The type of identifier ("issn" or "isbn")',
234 desc => 'The identifier value',
238 desc => 'The base URL for the resolver and instance',
243 desc => 'Returns a list of raw JSON objects representing the full-text holdings for a given ISBN or ISSN',
249 # Clear cache for specific lookups
250 sub delete_cached_holdings {
253 my $id_type = shift; # keep it simple for now, either 'issn' or 'isbn'
254 my $id_value = shift; # the normalized ISSN or ISBN
255 my $url_base = shift || $default_url_base;
258 $logger->warn("Deleting value [$id_value]");
259 # We'll use this in our cache key
260 foreach my $method ('open-ils.resolver.resolve_holdings.raw', 'open-ils.resolver.resolve_holdings') {
261 my $ckey = $prefix . $method . $url_base . $id_type . $id_value;
263 $logger->warn("Deleted cache key [$ckey]");
264 my $result = $cache->delete_cache($ckey);
266 $logger->warn("Result of deleting cache key: [$result]");
267 push @deleted_keys, $result;
270 return \@deleted_keys;
273 __PACKAGE__->register_method(
274 method => 'delete_holdings_cache',
275 api_name => 'open-ils.resolver.delete_cached_holdings',
280 Deletes the cached value of the full-text holdings for a given ISBN or ISSN
284 desc => 'The type of identifier ("issn" or "isbn")',
288 desc => 'The identifier value',
292 desc => 'The base URL for the resolver and instance',
297 desc => 'Deletes the cached value of the full-text holdings for a given ISBN or ISSN',