3 package OpenILS::Application::Fielder;
4 use OpenILS::Application;
5 use base qw/OpenILS::Application/;
7 use Unicode::Normalize;
8 use OpenSRF::EX qw/:try/;
10 use OpenSRF::AppSession;
11 use OpenSRF::Utils::SettingsClient;
12 use OpenSRF::Utils::Cache;
13 use OpenSRF::Utils::Logger qw/:level/;
15 use OpenILS::Utils::Fieldmapper;
16 use OpenSRF::Utils::JSON;
18 use OpenILS::Utils::CStoreEditor qw/:funcs/;
20 use Digest::MD5 qw(md5_hex);
23 use XML::LibXML::XPathContext;
26 use OpenILS::Application::Flattener;
29 $Data::Dumper::Indent = 0;
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'},
40 my $log = 'OpenSRF::Utils::Logger';
45 my $parser = XML::LibXML->new();
46 my $xslt = XML::LibXSLT->new();
48 my $xpc = XML::LibXML::XPathContext->new();
49 $xpc->registerNs($_, $namespace_map{$_}{ns}) for ( keys %namespace_map );
55 my $conf = OpenSRF::Utils::SettingsClient->new;
56 my $idl_file = $conf->config_value( 'IDL' );
58 $idl = $parser->parse_file( $idl_file );
60 $log->debug( 'IDL XML file loaded' );
62 $cache_timeout = $conf->config_value(
63 "apps", "open-ils.fielder", "app_settings", "cache_timeout" ) || 300;
65 $default_locale = $conf->config_value("default", "default_locale") || 'en-US';
71 $cache = OpenSRF::Utils::Cache->new('global');
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;
85 return OpenILS::Event->new('BAD_PARAMS', note => 'query is required') unless $query;
86 return OpenILS::Event->new('BAD_PARAMS', note => 'query must be a hash') unless ref($query) eq 'HASH';
87 return OpenILS::Event->new('BAD_PARAMS', note => 'query must not use function transforms')
88 if _fielder_fetch_query_is_bad($query, 0);
90 return OpenILS::Event->new('BAD_PARAMS', note => 'field list must not use function transforms')
91 if _fielder_fetch_query_is_bad($fields, 0);
94 my $obj_class = $self->{class_hint};
95 my $fm_class = $self->{class_name};
98 $fields = [ $fm_class->real_fields ];
101 $fields = [$fields] if (!ref($fields));
103 my $qstring = OpenSRF::Utils::JSON->perl2JSON( $query );
104 my $fstring = OpenSRF::Utils::JSON->perl2JSON( [ sort { $a cmp $b } @$fields ] );
106 $log->debug( 'Query Class: '. $obj_class );
107 $log->debug( 'Field list: '. $fstring );
108 $log->debug( 'Query: '. $qstring );
112 $key = 'open-ils.fielder_' . md5_hex(
121 $res = $cache->get_cache( $key );
124 $client->respond($_) for (@$res);
129 $res = new_editor()->json_query({
130 select => { $obj_class => $fields },
136 for my $value (@$res) {
137 $client->respond($value);
140 $client->respond_complete();
142 $cache->put_cache( $key => $res => $cache_timeout ) unless ($nocache);
146 sub _fielder_fetch_query_is_bad {
147 my ($query, $depth) = @_;
150 # arbitrarily assume that something naughty is going
151 # on if the original structure is 10 layers deep
155 if (ref $query eq 'HASH') {
156 return 1 if exists $query->{transform};
157 return 1 if exists $query->{params};
158 foreach my $key (keys %{ $query }) {
159 if (_fielder_fetch_query_is_bad($query->{$key}, $depth + 1)) {
164 } elsif (ref $query eq 'ARRAY') {
165 foreach my $entry (@{ $query }) {
166 if (_fielder_fetch_query_is_bad($entry, $depth + 1)) {
176 sub generate_methods {
178 for my $class_node ( $xpc->findnodes( '//idl:class[@oils_persist:field_safe="true"]', $idl->documentElement ) ) {
179 my $hint = $class_node->getAttribute('id');
180 my $fm = $class_node->getAttributeNS('http://open-ils.org/spec/opensrf/IDL/objects/v1','fieldmapper');
181 $log->debug("Fielder class_node $hint");
183 __PACKAGE__->register_method(
184 method => 'fielder_fetch',
185 api_name => 'open-ils.fielder.' . $hint,
187 class_name => "Fieldmapper::$fm",
194 $log->error("error generating Fielder methods: $e");
199 my ($self, $conn, $auth, $hint, $map) = @_;
201 my $e = new_editor(authtoken => $auth);
202 return $e->event unless $e->checkauth;
204 $key = 'flat_search_' . md5_hex(
206 OpenSRF::Utils::JSON->perl2JSON( $map )
209 $cache->put_cache( $key => { hint => $hint, map => $map } => $cache_timeout );
212 __PACKAGE__->register_method(
213 method => 'register_map',
214 api_name => 'open-ils.fielder.flattened_search.prepare',
218 {name => "auth", type => "string", desc => "auth token"},
219 {name => "hint", type => "string",
220 desc => "fieldmapper class hint of core object"},
221 {name => "map", type => "object", desc => q{
222 path-field mapping structure. See documentation under
223 docs/TechRef/Flattener in the Evergreen source tree.} }
227 A key used to reference a prepared flattened search on subsequent
228 calls to open-ils.fielder.flattened_search.execute},
234 sub execute_registered_flattened_search {
240 my $e = new_editor(authtoken => $auth);
241 return $e->event unless $e->checkauth;
244 my $blob = $cache->get_cache( $key ) or
245 return new OpenILS::Event('CACHE_MISS');
247 flattened_search( $self, $conn, $auth, $blob->{hint}, $blob->{map}, @_ )
248 if (ref($blob) and $blob->{hint} and $blob->{map});
251 __PACKAGE__->register_method(
252 method => 'execute_registered_flattened_search',
253 api_name => 'open-ils.fielder.flattened_search.execute',
258 {name => "auth", type => "string", desc => "auth token"},
259 {name => "key", type => "string",
260 desc => "Key for a registered map provided by open-ils.fielder.flattened_search.prepare"},
261 {name => "where", type => "object", desc => q{
262 simplified query clause (like the 'where' clause of a
263 json_query, but different). See documentation under
264 docs/TechRef/Flattener in the Evergreen source tree.} },
265 {name => "slo", type => "object", desc => q{
266 simplified sort/limit/offset object. See documentation under
267 docs/TechRef/Flattener in the Evergreen source tree.} }
271 A stream of objects flattened to your specifications. See
272 documentation under docs/TechRef/Flattener in the Evergreen
279 sub flattened_search {
280 my ($self, $conn, $auth, $hint, $map, $where, $slo) = @_;
282 # All but the last argument really are necessary.
285 my $e = new_editor(authtoken => $auth);
286 return $e->event unless $e->checkauth;
288 # Process the map to normalize it, and to get all our joins and fleshing
289 # structure into the jffolo.
292 OpenILS::Application::Flattener::process_map($hint, $map);
294 # Process the suppied where clause, using our map, to make the
296 my $filter = OpenILS::Application::Flattener::prepare_filter($map, $where);
298 # Process the supplied sort/limit/offset clause and use it to finish the
300 $jffolo = OpenILS::Application::Flattener::finish_jffolo(
301 $hint, $map, $jffolo, $slo
304 # Reach out and touch pcrud (could be cstore, if we wanted to offer
305 # this as a private service).
306 my $pcrud = create OpenSRF::AppSession("open-ils.pcrud");
307 my $req = $pcrud->request(
308 "open-ils.pcrud.search.$hint", $auth, $filter, $jffolo
311 # Stream back flattened results.
312 while (my $resp = $req->recv(timeout => 60)) {
314 OpenILS::Application::Flattener::process_result(
326 __PACKAGE__->register_method(
327 method => 'flattened_search',
328 api_name => 'open-ils.fielder.flattened_search',
333 {name => "auth", type => "string", desc => "auth token"},
334 {name => "hint", type => "string",
335 desc => "fieldmapper class hint of core object"},
336 {name => "map", type => "object", desc => q{
337 path-field mapping structure. See documentation under
338 docs/TechRef/Flattener in the Evergreen source tree.} },
339 {name => "where", type => "object", desc => q{
340 simplified query clause (like the 'where' clause of a
341 json_query, but different). See documentation under
342 docs/TechRef/Flattener in the Evergreen source tree.} },
343 {name => "slo", type => "object", desc => q{
344 simplified sort/limit/offset object. See documentation under
345 docs/TechRef/Flattener in the Evergreen source tree.} }
349 A stream of objects flattened to your specifications. See
350 documentation under docs/TechRef/Flattener in the Evergreen