]> git.evergreen-ils.org Git - working/Evergreen.git/blob - Open-ILS/src/perlmods/lib/OpenILS/Application/Fielder.pm
LP#1635737 Use new OpenSRF interval_to_seconds() context
[working/Evergreen.git] / Open-ILS / src / perlmods / lib / OpenILS / Application / Fielder.pm
1 # vim:et:ts=4:sw=4:
2
3 package OpenILS::Application::Fielder;
4 use OpenILS::Application;
5 use base qw/OpenILS::Application/;
6
7 use Unicode::Normalize;
8 use OpenSRF::EX qw/:try/;
9
10 use OpenSRF::AppSession;
11 use OpenSRF::Utils::SettingsClient;
12 use OpenSRF::Utils::Cache;
13 use OpenSRF::Utils::Logger qw/:level/;
14
15 use OpenILS::Utils::Fieldmapper;
16 use OpenSRF::Utils::JSON;
17
18 use OpenILS::Utils::CStoreEditor qw/:funcs/;
19
20 use Digest::MD5 qw(md5_hex);
21
22 use XML::LibXML;
23 use XML::LibXML::XPathContext;
24 use XML::LibXSLT;
25
26 use OpenILS::Application::Flattener;
27 use Data::Dumper;
28
29 $Data::Dumper::Indent = 0;
30
31 our %namespace_map = (
32     oils_persist=> {ns => 'http://open-ils.org/spec/opensrf/IDL/persistence/v1'},
33     oils_obj    => {ns => 'http://open-ils.org/spec/opensrf/IDL/objects/v1'},
34     idl         => {ns => 'http://opensrf.org/spec/IDL/base/v1'},
35     reporter    => {ns => 'http://open-ils.org/spec/opensrf/IDL/reporter/v1'},
36     perm        => {ns => 'http://open-ils.org/spec/opensrf/IDL/permacrud/v1'},
37 );
38
39
40 my $log = 'OpenSRF::Utils::Logger';
41
42 my $cache;
43 my $cache_timeout;
44 my $default_locale;
45 my $parser = XML::LibXML->new();
46 my $xslt = XML::LibXSLT->new();
47
48 my $xpc = XML::LibXML::XPathContext->new();
49 $xpc->registerNs($_, $namespace_map{$_}{ns}) for ( keys %namespace_map );
50
51 my $idl;
52
53 sub initialize {
54
55     my $conf = OpenSRF::Utils::SettingsClient->new;
56     my $idl_file = $conf->config_value( 'IDL' );
57
58     $idl = $parser->parse_file( $idl_file );
59
60     $log->debug( 'IDL XML file loaded' );
61
62     $cache_timeout = $conf->config_value(
63             "apps", "open-ils.fielder", "app_settings", "cache_timeout" ) || 300;
64
65     $default_locale = $conf->config_value("default", "default_locale") || 'en-US';
66
67     generate_methods();
68
69 }
70 sub child_init {
71     $cache = OpenSRF::Utils::Cache->new('global');
72 }
73
74 sub fielder_fetch {
75     my $self = shift;
76     my $client = shift;
77     my $obj = shift;
78
79     my $locale = $self->session->session_locale || $default_locale;
80     my $query = $obj->{query};
81     my $nocache = $obj->{cache} ? 0 : 1;
82     my $fields = $obj->{fields};
83     my $distinct = $obj->{distinct} ? 1 : 0;
84
85     return undef unless $query;
86
87     my $obj_class = $self->{class_hint};
88     my $fm_class = $self->{class_name};
89
90     if (!$fields) {
91         $fields = [ $fm_class->real_fields ];
92     }
93
94     $fields = [$fields] if (!ref($fields));
95
96     my $qstring = OpenSRF::Utils::JSON->perl2JSON( $query );
97     my $fstring = OpenSRF::Utils::JSON->perl2JSON( [ sort { $a cmp $b } @$fields ] );
98
99     $log->debug( 'Query Class: '. $obj_class );
100     $log->debug( 'Field list: '. $fstring );
101     $log->debug( 'Query: '. $qstring );
102
103     my ($key,$res);
104     unless ($nocache) {
105         $key = 'open-ils.fielder_' . md5_hex(
106             $self->api_name . 
107             $qstring .
108             $fstring .
109             $distinct .
110             $obj_class .
111             $locale
112         );
113
114         $res = $cache->get_cache( $key );
115
116         if ($res) {
117             $client->respond($_) for (@$res);
118             return undef;
119         }
120     }
121
122     $res = new_editor()->json_query({
123         select  => { $obj_class => $fields },
124         from    => $obj_class,
125         where   => $query,
126         distinct=> $distinct
127     });
128
129     for my $value (@$res) {
130         $client->respond($value);
131     }
132
133     $client->respond_complete();
134
135     $cache->put_cache( $key => $res => $cache_timeout ) unless ($nocache);
136     return undef;
137 }
138
139 sub generate_methods {
140     try {
141         for my $class_node ( $xpc->findnodes( '//idl:class[@oils_persist:field_safe="true"]', $idl->documentElement ) ) {
142             my $hint = $class_node->getAttribute('id');
143             my $fm = $class_node->getAttributeNS('http://open-ils.org/spec/opensrf/IDL/objects/v1','fieldmapper');
144             $log->debug("Fielder class_node $hint");
145         
146             __PACKAGE__->register_method(
147                 method          => 'fielder_fetch',
148                 api_name        => 'open-ils.fielder.' . $hint,
149                 class_hint      => $hint,
150                 class_name      => "Fieldmapper::$fm",
151                 stream          => 1,
152                 argc            => 1
153             );
154         }
155     } catch Error with {
156         my $e = shift;
157         $log->error("error generating Fielder methods: $e");
158     };
159 }
160
161 sub register_map {
162     my ($self, $conn, $auth, $hint, $map) = @_;
163
164     my $e = new_editor(authtoken => $auth);
165     return $e->event unless $e->checkauth;
166
167     $key = 'flat_search_' . md5_hex(
168         $hint .
169         OpenSRF::Utils::JSON->perl2JSON( $map )
170     );
171
172     $cache->put_cache( $key => { hint => $hint, map => $map } => $cache_timeout );
173 }
174
175 __PACKAGE__->register_method(
176     method          => 'register_map',
177     api_name        => 'open-ils.fielder.flattened_search.prepare',
178     argc            => 3,
179     signature       => {
180         params => [
181             {name => "auth", type => "string", desc => "auth token"},
182             {name => "hint", type => "string",
183                 desc => "fieldmapper class hint of core object"},
184             {name => "map", type => "object", desc => q{
185                 path-field mapping structure. See documentation under
186                 docs/TechRef/Flattener in the Evergreen source tree.} }
187         ],
188         return => {
189             desc => q{
190                 A key used to reference a prepared flattened search on subsequent
191                 calls to open-ils.fielder.flattened_search.execute},
192             type => "string"
193         }
194     }
195 );
196
197 sub execute_registered_flattened_search {
198     my $self = shift;
199     my $conn = shift;
200     my $auth = shift;
201     my $key  = shift;
202
203     my $e = new_editor(authtoken => $auth);
204     return $e->event unless $e->checkauth;
205     $e->disconnect;
206
207     my $blob = $cache->get_cache( $key ) or
208         return new OpenILS::Event('CACHE_MISS');
209
210     flattened_search( $self, $conn, $auth, $blob->{hint}, $blob->{map}, @_ )
211         if (ref($blob) and $blob->{hint} and $blob->{map});
212 }
213
214 __PACKAGE__->register_method(
215     method          => 'execute_registered_flattened_search',
216     api_name        => 'open-ils.fielder.flattened_search.execute',
217     stream          => 1,
218     argc            => 5,
219     signature       => {
220         params => [
221             {name => "auth", type => "string", desc => "auth token"},
222             {name => "key", type => "string",
223                 desc => "Key for a registered map provided by open-ils.fielder.flattened_search.prepare"},
224             {name => "where", type => "object", desc => q{
225                 simplified query clause (like the 'where' clause of a
226                 json_query, but different). See documentation under
227                 docs/TechRef/Flattener in the Evergreen source tree.} },
228             {name => "slo", type => "object", desc => q{
229                 simplified sort/limit/offset object. See documentation under
230                 docs/TechRef/Flattener in the Evergreen source tree.} }
231         ],
232         return => {
233             desc => q{
234                 A stream of objects flattened to your specifications. See
235                 documentation under docs/TechRef/Flattener in the Evergreen
236                 source tree.},
237             type => "object"
238         }
239     }
240 );
241
242 sub flattened_search {
243     my ($self, $conn, $auth, $hint, $map, $where, $slo) = @_;
244
245     # All but the last argument really are necessary.
246     $slo ||= {};
247
248     my $e = new_editor(authtoken => $auth);
249     return $e->event unless $e->checkauth;
250
251     # Process the map to normalize it, and to get all our joins and fleshing
252     # structure into the jffolo.
253     my $jffolo;
254     ($map, $jffolo) =
255         OpenILS::Application::Flattener::process_map($hint, $map);
256
257     # Process the suppied where clause, using our map, to make the
258     # filter.
259     my $filter = OpenILS::Application::Flattener::prepare_filter($map, $where);
260
261     # Process the supplied sort/limit/offset clause and use it to finish the
262     # jffolo.
263     $jffolo = OpenILS::Application::Flattener::finish_jffolo(
264         $hint, $map, $jffolo, $slo
265     );
266
267     # Reach out and touch pcrud (could be cstore, if we wanted to offer
268     # this as a private service).
269     my $pcrud = create OpenSRF::AppSession("open-ils.pcrud");
270     my $req = $pcrud->request(
271         "open-ils.pcrud.search.$hint", $auth, $filter, $jffolo
272     );
273
274     # Stream back flattened results.
275     while (my $resp = $req->recv(timeout => 60)) {
276         $conn->respond(
277             OpenILS::Application::Flattener::process_result(
278                 $map, $resp->content
279             )
280         );
281     }
282
283     # Clean up.
284     $pcrud->kill_me;
285
286     return;
287 }
288
289 __PACKAGE__->register_method(
290     method          => 'flattened_search',
291     api_name        => 'open-ils.fielder.flattened_search',
292     stream          => 1,
293     argc            => 5,
294     signature       => {
295         params => [
296             {name => "auth", type => "string", desc => "auth token"},
297             {name => "hint", type => "string",
298                 desc => "fieldmapper class hint of core object"},
299             {name => "map", type => "object", desc => q{
300                 path-field mapping structure. See documentation under
301                 docs/TechRef/Flattener in the Evergreen source tree.} },
302             {name => "where", type => "object", desc => q{
303                 simplified query clause (like the 'where' clause of a
304                 json_query, but different). See documentation under
305                 docs/TechRef/Flattener in the Evergreen source tree.} },
306             {name => "slo", type => "object", desc => q{
307                 simplified sort/limit/offset object. See documentation under
308                 docs/TechRef/Flattener in the Evergreen source tree.} }
309         ],
310         return => {
311             desc => q{
312                 A stream of objects flattened to your specifications. See
313                 documentation under docs/TechRef/Flattener in the Evergreen
314                 source tree.},
315             type => "object"
316         }
317     }
318 );
319
320 1;
321