]> git.evergreen-ils.org Git - working/Evergreen.git/blob - Open-ILS/src/perlmods/lib/OpenILS/WWW/AddedContent.pm
Start of AC by record ID from 2011 hackfest
[working/Evergreen.git] / Open-ILS / src / perlmods / lib / OpenILS / WWW / AddedContent.pm
1 package OpenILS::WWW::AddedContent;
2 use strict; use warnings;
3
4 use CGI;
5 use Apache2::Log;
6 use Apache2::Const -compile => qw(OK REDIRECT DECLINED NOT_FOUND :log);
7 use APR::Const    -compile => qw(:error SUCCESS);
8 use Apache2::RequestRec ();
9 use Apache2::RequestIO ();
10 use Apache2::RequestUtil;
11 use Data::Dumper;
12 use UNIVERSAL::require;
13
14 use OpenSRF::EX qw(:try);
15 use OpenSRF::Utils::Cache;
16 use OpenSRF::System;
17 use OpenSRF::Utils::Logger qw/$logger/;
18 use OpenILS::Utils::CStoreEditor;
19
20 use LWP::UserAgent;
21 use MIME::Base64;
22
23 my $AC = __PACKAGE__;
24
25
26 # set the bootstrap config when this module is loaded
27 my $bs_config;
28
29 sub import {
30     my $self = shift;
31     $bs_config = shift;
32 }
33
34
35 my $handler; # added content handler class handle
36 my $cache; # memcache handle
37 my $net_timeout; # max seconds to wait for a response from the added content vendor
38 my $max_errors; # max consecutive lookup failures before added content is temporarily disabled
39 my $error_countdown; # current consecutive errors countdown
40
41 # number of seconds to wait before next lookup 
42 # is attempted after lookups have been disabled
43 my $error_retry_timeout;
44
45
46 sub child_init {
47
48     OpenSRF::System->bootstrap_client( config_file => $bs_config );
49     $cache = OpenSRF::Utils::Cache->new;
50
51     my $sclient = OpenSRF::Utils::SettingsClient->new();
52     my $ac_data = $sclient->config_value("added_content");
53
54     return Apache2::Const::OK unless $ac_data;
55     my $ac_handler = $ac_data->{module};
56     return Apache2::Const::OK unless $ac_handler;
57
58     $net_timeout = $ac_data->{timeout} || 1;
59     $error_countdown = $max_errors = $ac_data->{max_errors} || 10;
60     $error_retry_timeout = $ac_data->{retry_timeout} || 600;
61
62     $logger->debug("Attempting to load Added Content handler: $ac_handler");
63
64     $ac_handler->use;
65
66     if($@) {    
67         $logger->error("Unable to load Added Content handler [$ac_handler]: $@"); 
68         return Apache2::Const::OK; 
69     }
70
71     $handler = $ac_handler->new($ac_data);
72     $logger->debug("added content loaded handler: $handler");
73     return Apache2::Const::OK;
74 }
75
76
77 sub handler {
78
79     my $r   = shift;
80     return Apache2::Const::DECLINED if (-e $r->filename);
81
82     my $cgi = CGI->new;
83     my @path_parts = split( /\//, $r->unparsed_uri );
84     my $type = $path_parts[-3];
85     my $format = $path_parts[-2];
86     my $id = $path_parts[-1];
87     my $res;
88
89     child_init() unless $handler;
90
91     return Apache2::Const::NOT_FOUND unless $handler and $type and $format and $id;
92
93     my $err;
94     my $data;
95     my $method = "${type}_${format}";
96
97     return Apache2::Const::NOT_FOUND unless $handler->can($method);
98     return $res if defined($res = $AC->serve_from_cache($type, $format, $id));
99     return Apache2::Const::NOT_FOUND unless $AC->lookups_enabled;
100
101     my $key_data = get_rec_keys($id);
102     my @isbns = grep {$_->{tag} eq '020'} @$key_data;
103     my @upcs = grep {$_->{tag} eq '024'} @$key_data;
104
105     my $keys = {
106         isbn => [map {$_->{value}} @isbns],
107         upc => [map {$_->{value}} @upcs]
108     };
109
110     # TODO clean isbn
111
112     # XXX DEBUG
113     use OpenSRF::Utils::JSON;
114     $logger->info("Added Content Keys: " . OpenSRF::Utils::JSON->perl2JSON($keys));
115
116     try {
117         $data = $handler->$method($keys);
118     } catch Error with {
119         $err = shift;
120         decr_error_countdown();
121         $logger->debug("added content handler failed: $method($id) => $err");
122     };
123
124     return Apache2::Const::NOT_FOUND if $err;
125     next unless $data;
126
127     if(!$data) {
128         # if the AC lookup found no corresponding data, cache that information
129         $logger->debug("added content handler returned no results $method($id)") unless $data;
130         $AC->cache_result($type, $format, $id, {nocontent=>1});
131         return Apache2::Const::NOT_FOUND;
132     }
133     
134     $AC->print_content($data);
135     $AC->cache_result($type, $format, $id, $data);
136
137     reset_error_countdown();
138     return Apache2::Const::OK;
139 }
140
141 # returns [{tag => $tag, value => $value}, {tag => $tag2, value => $value2}]
142 sub get_rec_keys {
143     my $id = shift;
144     return OpenILS::Utils::CStoreEditor->new->json_query({
145         select => {mfr => ['tag', 'value']},
146         from => 'mfr',
147         where => {
148             record => $id,
149             '-or' => [
150                 {
151                     '-and' => [
152                         {tag => '020'},
153                         {subfield => 'a'}
154                     ]
155                 }, {
156                     '-and' => [
157                         {tag => '024'},
158                         {subfield => 'a'},
159                         {ind1 => 1}
160                     ]
161                 }
162             ]
163         }
164     });
165 }
166
167 sub print_content {
168     my($class, $data, $from_cache) = @_;
169     return Apache2::Const::NOT_FOUND if $data->{nocontent};
170
171     my $ct = $data->{content_type};
172     my $content = $data->{content};
173     print "Content-type: $ct\n\n";
174
175     if($data->{binary}) {
176         binmode STDOUT;
177         # if it hasn't been cached yet, it's still in binary form
178         print( ($from_cache) ? decode_base64($content) : $content );
179     } else {
180         print $content;
181     }
182
183
184     return Apache2::Const::OK;
185 }
186
187
188
189
190 # returns an HTPP::Response object
191 sub get_url {
192     my( $self, $url ) = @_;
193
194     $logger->info("added content getting [timeout=$net_timeout, errors_remaining=$error_countdown] URL = $url");
195     my $agent = LWP::UserAgent->new(timeout => $net_timeout);
196
197     my $res = $agent->get($url); 
198     $logger->info("added content request returned with code " . $res->code);
199     die "added content request failed: " . $res->status_line ."\n" unless $res->is_success;
200
201     return $res;
202 }
203
204 sub lookups_enabled {
205     if( $cache->get_cache('ac.no_lookup') ) {
206         $logger->info("added content lookup disabled");
207         return undef;
208     }
209     return 1;
210 }
211
212 sub disable_lookups {
213     $cache->put_cache('ac.no_lookup', 1, $error_retry_timeout);
214 }
215
216 sub decr_error_countdown {
217     $error_countdown--;
218     if($error_countdown < 1) {
219         $logger->warn("added content error count exhausted.  Disabling lookups for $error_retry_timeout seconds");
220         $AC->disable_lookups;
221     }
222 }
223
224 sub reset_error_countdown {
225     $error_countdown = $max_errors;
226 }
227
228 sub cache_result {
229     my($class, $type, $format, $key, $data) = @_;
230     $logger->debug("caching $type/$format/$key");
231     $data->{content} = encode_base64($data->{content}) if $data->{binary};
232     return $cache->put_cache("ac.$type.$format.$key", $data);
233 }
234
235 sub serve_from_cache {
236     my($class, $type, $format, $key) = @_;
237     my $data = $cache->get_cache("ac.$type.$format.$key");
238     return undef unless $data;
239     $logger->debug("serving $type/$format/$key from cache");
240     return $class->print_content($data, 1);
241 }
242
243
244
245 1;