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