]> git.evergreen-ils.org Git - working/Evergreen.git/blob - Open-ILS/src/perlmods/OpenILS/Application/Ingest.pm
pass the ID not the bib object to the reporter.simple_rec_update stored proc
[working/Evergreen.git] / Open-ILS / src / perlmods / OpenILS / Application / Ingest.pm
1 package OpenILS::Application::Ingest;
2 use OpenILS::Application;
3 use base qw/OpenILS::Application/;
4
5 use Unicode::Normalize;
6 use OpenSRF::EX qw/:try/;
7
8 use OpenSRF::AppSession;
9 use OpenSRF::Utils::SettingsClient;
10 use OpenSRF::Utils::Logger qw/:level/;
11
12 use OpenILS::Application::AppUtils;
13 use OpenILS::Utils::ScriptRunner;
14 use OpenILS::Utils::Fieldmapper;
15 use OpenSRF::Utils::JSON;
16
17 use OpenILS::Utils::Fieldmapper;
18
19 use XML::LibXML;
20 use XML::LibXSLT;
21 use Time::HiRes qw(time);
22
23 our %supported_formats = (
24     mods33    => {ns => 'http://www.loc.gov/mods/v3'},
25     mods32    => {ns => 'http://www.loc.gov/mods/v3'},
26     mods3    => {ns => 'http://www.loc.gov/mods/v3'},
27     mods    => {ns => 'http://www.loc.gov/mods/'},
28     marcxml    => {ns => 'http://www.loc.gov/MARC21/slim'},
29     srw_dc    => {ns => 'info:srw/schema/1/dc-schema'},
30     oai_dc    => {ns => 'http://www.openarchives.org/OAI/2.0/oai_dc/'},
31     rdf_dc    => {ns => 'http://www.w3.org/1999/02/22-rdf-syntax-ns#'},
32     atom    => {ns => 'http://www.w3.org/2005/Atom'},
33     rss091    => {ns => 'http://my.netscape.com/rdf/simple/0.9/'},
34     rss092    => {ns => ''},
35     rss093    => {ns => ''},
36     rss094    => {ns => ''},
37     rss10    => {ns => 'http://purl.org/rss/1.0/'},
38     rss11    => {ns => 'http://purl.org/net/rss1.1#'},
39     rss2    => {ns => ''},
40 );
41
42
43 my $log = 'OpenSRF::Utils::Logger';
44
45 my  $parser = XML::LibXML->new();
46 my  $xslt = XML::LibXSLT->new();
47
48 my  $mods_sheet;
49 my  $mads_sheet;
50 my  $xpathset = {};
51 sub initialize {}
52 sub child_init {}
53
54 sub post_init {
55
56     unless (keys %$xpathset) {
57         $log->debug("Running post_init", DEBUG);
58
59         my $xsldir = OpenSRF::Utils::SettingsClient->new->config_value(dirs => 'xsl');
60
61         unless ($supported_formats{mods}{xslt}) {
62             $log->debug("Loading MODS XSLT", DEBUG);
63              my $xslt_doc = $parser->parse_file( $xsldir . "/MARC21slim2MODS.xsl");
64             $supported_formats{mods}{xslt} = $xslt->parse_stylesheet( $xslt_doc );
65         }
66
67         unless ($supported_formats{mods3}{xslt}) {
68             $log->debug("Loading MODS v3 XSLT", DEBUG);
69              my $xslt_doc = $parser->parse_file( $xsldir . "/MARC21slim2MODS3.xsl");
70             $supported_formats{mods3}{xslt} = $xslt->parse_stylesheet( $xslt_doc );
71         }
72
73         unless ($supported_formats{mods32}{xslt}) {
74             $log->debug("Loading MODS v32 XSLT", DEBUG);
75              my $xslt_doc = $parser->parse_file( $xsldir . "/MARC21slim2MODS32.xsl");
76             $supported_formats{mods32}{xslt} = $xslt->parse_stylesheet( $xslt_doc );
77         }
78
79         unless ($supported_formats{mods33}{xslt}) {
80             $log->debug("Loading MODS v33 XSLT", DEBUG);
81              my $xslt_doc = $parser->parse_file( $xsldir . "/MARC21slim2MODS33.xsl");
82             $supported_formats{mods33}{xslt} = $xslt->parse_stylesheet( $xslt_doc );
83         }
84
85         my $req = OpenSRF::AppSession
86                 ->create('open-ils.cstore')
87                 
88                 # XXX testing new metabib field use for faceting
89                 #->request( 'open-ils.cstore.direct.config.metabib_field.search.atomic', { id => { '!=' => undef } } )
90                 ->request( 'open-ils.cstore.direct.config.metabib_field.search.atomic', { search_field => 't' } )
91
92                 ->gather(1);
93
94         if (ref $req and @$req) {
95             for my $f (@$req) {
96                 $xpathset->{ $f->field_class }->{ $f->name }->{xpath} = $f->xpath;
97                 $xpathset->{ $f->field_class }->{ $f->name }->{id} = $f->id;
98                 $xpathset->{ $f->field_class }->{ $f->name }->{format} = $f->format;
99                 $log->debug("Loaded XPath from DB: ".$f->field_class." => ".$f->name." : ".$f->xpath, DEBUG);
100             }
101         }
102     }
103 }
104
105 # --------------------------------------------------------------------------------
106 # Biblio ingest
107
108 package OpenILS::Application::Ingest::Biblio;
109 use base qw/OpenILS::Application::Ingest/;
110 use Unicode::Normalize;
111
112 sub rw_biblio_ingest_single_object {
113     my $self = shift;
114     my $client = shift;
115     my $bib = shift;
116
117     my ($blob) = $self->method_lookup("open-ils.ingest.full.biblio.object.readonly")->run($bib);
118     return undef unless ($blob);
119
120     $bib->fingerprint( $blob->{fingerprint}->{fingerprint} );
121     $bib->quality( $blob->{fingerprint}->{quality} );
122
123     my $cstore = OpenSRF::AppSession->connect('open-ils.cstore');
124
125     my $xact = $cstore->request('open-ils.cstore.transaction.begin')->gather(1);
126     my $tmp;
127
128     # update uri stuff ...
129
130     # gather URI call numbers for this record
131     my $uri_cns = $u->{call_number} = $cstore->request(
132         'open-ils.cstore.direct.asset.call_number.id_list.atomic' => { record => $bib->id, label => '##URI##' }
133     )->gather(1);
134
135     if (@$uri_cns) {
136         # gather the maps for those call numbers
137         my $uri_maps = $u->{call_number} = $cstore->request(
138             'open-ils.cstore.direct.asset.uri_call_number_map.id_list.atomic' => { call_number => $uri_cns }
139         )->gather(1);
140     
141         # delete the old maps
142         $cstore->request( 'open-ils.cstore.direct.asset.uri_call_number_map.delete' => $_ )->gather(1) for (@$uri_maps);
143     
144         # and delete the call numbers if there are no more URIs
145         if (!@{ $blob->{uri} }) {
146             $cstore->request( 'open-ils.cstore.direct.asset.call_number.delete' => $_ )->gather(1) for (@$uri_cns);
147         }
148     }
149
150     # now, add CNs, URIs and maps
151     my %new_cns_by_owner;
152     my %new_uris_by_owner;
153     for my $u ( @{ $blob->{uri} } ) {
154
155         my $owner = $u->{call_number}->owning_lib;
156
157         if ($u->{call_number}->isnew) {
158             if ($new_cns_by_owner{$owner}) {
159                 $u->{call_number} = $new_cns_by_owner{$owner};
160             } else {
161                 $u->{call_number}->clear_id;
162                 $u->{call_number} = $new_cns_by_owner{$owner} = $cstore->request(
163                     'open-ils.cstore.direct.asset.call_number.create' => $u->{call_number}
164                 )->gather(1);
165             }
166         }
167
168         if ($u->{uri}->isnew) {
169             if ($new_uris_by_owner{$owner}) {
170                 $u->{uri} = $new_uris_by_owner{$owner};
171             } else {
172                 $u->{uri} = $new_uris_by_owner{$owner} = $cstore->request(
173                     'open-ils.cstore.direct.asset.uri.create' => $u->{uri}
174                 )->gather(1);
175             }
176         }
177
178         # Check for an existing CN-URI map
179         $tmp = $cstore->request(
180             'open-ils.cstore.direct.asset.uri_call_number_map.id_list',
181             { call_number => $u->{call_number}->id, uri => $u->{uri}->id }
182         )->gather(1);
183
184         next if ($tmp);
185
186         my $umap = Fieldmapper::asset::uri_call_number_map->new;
187         $umap->uri($u->{uri}->id);
188         $umap->call_number($u->{call_number}->id);
189
190         $cstore->request( 'open-ils.cstore.direct.asset.uri_call_number_map.create' => $umap )->gather(1);
191     }
192
193     # update full_rec stuff ...
194     $tmp = $cstore->request(
195         'open-ils.cstore.direct.metabib.full_rec.id_list.atomic',
196         { record => $bib->id }
197     )->gather(1);
198
199     $cstore->request( 'open-ils.cstore.direct.metabib.full_rec.delete' => $_ )->gather(1) for (@$tmp);
200     $cstore->request( 'open-ils.cstore.direct.metabib.full_rec.create' => $_ )->gather(1) for (@{ $blob->{full_rec} });
201
202     # update rec_descriptor stuff ...
203     $tmp = $cstore->request(
204         'open-ils.cstore.direct.metabib.record_descriptor.id_list.atomic',
205         { record => $bib->id }
206     )->gather(1);
207
208     $cstore->request( 'open-ils.cstore.direct.metabib.record_descriptor.delete' => $_ )->gather(1) for (@$tmp);
209     $cstore->request( 'open-ils.cstore.direct.metabib.record_descriptor.create' => $blob->{descriptor} )->gather(1);
210
211     # deal with classed fields...
212     for my $class ( qw/title author subject keyword series/ ) {
213         $tmp = $cstore->request(
214             "open-ils.cstore.direct.metabib.${class}_field_entry.id_list.atomic",
215             { source => $bib->id }
216         )->gather(1);
217
218         $cstore->request( "open-ils.cstore.direct.metabib.${class}_field_entry.delete" => $_ )->gather(1) for (@$tmp);
219     }
220     for my $obj ( @{ $blob->{field_entries} } ) {
221         my $class = $obj->class_name;
222         $class =~ s/^Fieldmapper:://o;
223         $class =~ s/::/./go;
224         $cstore->request( "open-ils.cstore.direct.$class.create" => $obj )->gather(1);
225     }
226
227     # update MR map ...
228
229     $tmp = $cstore->request(
230         'open-ils.cstore.direct.metabib.metarecord_source_map.search.atomic',
231         { source => $bib->id }
232     )->gather(1);
233
234     $cstore->request( 'open-ils.cstore.direct.metabib.metarecord_source_map.delete' => $_->id )->gather(1) for (@$tmp);
235
236     # get the old MRs
237     my $old_mrs = $cstore->request(
238         'open-ils.cstore.direct.metabib.metarecord.search.atomic' => { id => [map { $_->metarecord } @$tmp] }
239     )->gather(1) if (@$tmp);
240
241     $old_mrs = [] if (!ref($old_mrs));
242
243     my $mr;
244     for my $m (@$old_mrs) {
245         if ($m->fingerprint eq $bib->fingerprint) {
246             $mr = $m;
247         } else {
248             my $others = $cstore->request(
249                 'open-ils.cstore.direct.metabib.metarecord_source_map.id_list.atomic' => { metarecord => $m->id }
250             )->gather(1);
251
252             if (!@$others) {
253                 $cstore->request(
254                     'open-ils.cstore.direct.metabib.metarecord.delete' => $m->id
255                 )->gather(1);
256             }
257
258             $m->isdeleted(1);
259         }
260     }
261
262     my $holds;
263     if (!$mr) {
264         # Get the matchin MR, if any.
265         $mr = $cstore->request(
266             'open-ils.cstore.direct.metabib.metarecord.search',
267             { fingerprint => $bib->fingerprint }
268         )->gather(1);
269
270         $holds = $cstore->request(
271             'open-ils.cstore.direct.action.hold_request.search.atomic',
272             { hold_type => 'M', target => [ map { $_->id } grep { $_->isdeleted } @$old_mrs ] }
273         )->gather(1) if (@$old_mrs);
274
275         if ($mr) {
276             for my $h (@$holds) {
277                 $h->target($mr);
278                 $cstore->request( 'open-ils.cstore.direct.action.hold_request.update' => $h )->gather(1);
279                 $h->ischanged(1);
280             }
281         }
282     }
283
284     if (!$mr) {
285         $mr = new Fieldmapper::metabib::metarecord;
286         $mr->fingerprint( $bib->fingerprint );
287         $mr->master_record( $bib->id );
288         $mr->id(
289             $cstore->request(
290                 "open-ils.cstore.direct.metabib.metarecord.create",
291                 $mr => { quiet => 'true' }
292             )->gather(1)
293         );
294
295         for my $h (grep { !$_->ischanged } @$holds) {
296             $h->target($mr);
297             $cstore->request( 'open-ils.cstore.direct.action.hold_request.update' => $h )->gather(1);
298         }
299     } else {
300         my $mrm = $cstore->request(
301             'open-ils.cstore.direct.metabib.metarecord_source_map.search.atomic',
302             { metarecord => $mr->id }
303         )->gather(1);
304
305         if (@$mrm) {
306             my $best = $cstore->request(
307                 "open-ils.cstore.direct.biblio.record_entry.search",
308                 { id => [ map { $_->source } @$mrm ] },
309                 { 'select'    => { bre => [ qw/id quality/ ] },
310                   order_by    => { bre => "quality desc" },
311                   limit        => 1,
312                 }
313             )->gather(1);
314
315             if ($best->quality > $bib->quality) {
316                 $mr->master_record($best->id);
317             } else {
318                 $mr->master_record($bib->id);
319             }
320         } else {
321             $mr->master_record($bib->id);
322         }
323
324         $mr->clear_mods;
325
326         $cstore->request( 'open-ils.cstore.direct.metabib.metarecord.update' => $mr )->gather(1);
327     }
328
329     my $mrm = new Fieldmapper::metabib::metarecord_source_map;
330     $mrm->source($bib->id);
331     $mrm->metarecord($mr->id);
332
333     $cstore->request( 'open-ils.cstore.direct.metabib.metarecord_source_map.create' => $mrm )->gather(1);
334     $cstore->request( 'open-ils.cstore.direct.biblio.record_entry.update' => $bib )->gather(1);
335
336     $cstore->request( 'open-ils.cstore.json_query.atomic' => { from => [ 'reporter.simple_rec_update', $bib->id ] } )->gather(1);
337
338     $cstore->request( 'open-ils.cstore.transaction.commit' )->gather(1) || return undef;;
339     $cstore->disconnect;
340
341     return $bib->id;
342 }
343 __PACKAGE__->register_method(  
344     api_name    => "open-ils.ingest.full.biblio.object",
345     method        => "rw_biblio_ingest_single_object",
346     api_level    => 1,
347     argc        => 1,
348 );                      
349
350 sub rw_biblio_ingest_single_record {
351     my $self = shift;
352     my $client = shift;
353     my $rec = shift;
354
355     OpenILS::Application::Ingest->post_init();
356     my $cstore = OpenSRF::AppSession->connect( 'open-ils.cstore' );
357     $cstore->request('open-ils.cstore.transaction.begin')->gather(1);
358
359     my $r = $cstore->request( 'open-ils.cstore.direct.biblio.record_entry.retrieve' => $rec )->gather(1);
360
361     $cstore->request('open-ils.cstore.transaction.rollback')->gather(1);
362     $cstore->disconnect;
363
364     return undef unless ($r and @$r);
365
366     return ($self->method_lookup("open-ils.ingest.full.biblio.object")->run($r))[0];
367 }
368 __PACKAGE__->register_method(  
369     api_name    => "open-ils.ingest.full.biblio.record",
370     method        => "rw_biblio_ingest_single_record",
371     api_level    => 1,
372     argc        => 1,
373 );                      
374
375 sub rw_biblio_ingest_record_list {
376     my $self = shift;
377     my $client = shift;
378     my @rec = ref($_[0]) ? @{ $_[0] } : @_ ;
379
380     OpenILS::Application::Ingest->post_init();
381     my $cstore = OpenSRF::AppSession->connect( 'open-ils.cstore' );
382     $cstore->request('open-ils.cstore.transaction.begin')->gather(1);
383
384     my $r = $cstore->request( 'open-ils.cstore.direct.biblio.record_entry.search.atomic' => { id => \@rec } )->gather(1);
385
386     $cstore->request('open-ils.cstore.transaction.rollback')->gather(1);
387     $cstore->disconnect;
388
389     return undef unless ($r and @$r);
390
391     my $count = 0;
392     for (@$r) {
393         if (($self->method_lookup("open-ils.ingest.full.biblio.object")->run($_))[0]) {
394             $count++
395         }
396     }
397     return $count;
398 }
399 __PACKAGE__->register_method(  
400     api_name    => "open-ils.ingest.full.biblio.record_list",
401     method        => "rw_biblio_ingest_record_list",
402     api_level    => 1,
403     argc        => 1,
404 );                      
405
406 sub ro_biblio_ingest_single_object {
407     my $self = shift;
408     my $client = shift;
409     my $bib = shift;
410     my $xml = OpenILS::Application::AppUtils->entityize($bib->marc);
411     my $max_cn = shift;
412     my $max_uri = shift;
413
414     my $cstore = OpenSRF::AppSession->connect( 'open-ils.cstore' );
415
416     if (!$max_cn) {
417         my $cn = $cstore->request( 'open-ils.cstore.direct.asset.call_number.search' => { id => { '!=' => undef } }, { limit => 1, order_by => { acn => 'id desc' } } )->gather(1);
418         $max_cn = int($cn->id) + 1000;
419     }
420
421     if (!$max_uri) {
422         my $cn = $cstore->request( 'open-ils.cstore.direct.asset.call_number.search' => { id => { '!=' => undef } }, { limit => 1, order_by => { acn => 'id desc' } } )->gather(1);
423         $max_uri = int($cn->id) + 1000;
424     }
425
426     $cstore->disconnect;
427
428     my $document = $parser->parse_string($xml);
429
430     my @uris = $self->method_lookup("open-ils.ingest.856_uri.object")->run($bib, $max_cn, $max_uri);
431     my @mfr = $self->method_lookup("open-ils.ingest.flat_marc.biblio.xml")->run($document);
432     my @mXfe = $self->method_lookup("open-ils.ingest.extract.field_entry.all.xml")->run($document);
433     my ($fp) = $self->method_lookup("open-ils.ingest.fingerprint.xml")->run($xml);
434     my ($rd) = $self->method_lookup("open-ils.ingest.descriptor.xml")->run($xml);
435
436     $_->source($bib->id) for (@mXfe);
437     $_->record($bib->id) for (@mfr);
438     $rd->record($bib->id) if ($rd);
439
440     return { full_rec => \@mfr, field_entries => \@mXfe, fingerprint => $fp, descriptor => $rd, uri => \@uris };
441 }
442 __PACKAGE__->register_method(  
443     api_name    => "open-ils.ingest.full.biblio.object.readonly",
444     method        => "ro_biblio_ingest_single_object",
445     api_level    => 1,
446     argc        => 1,
447 );                      
448
449 sub ro_biblio_ingest_single_xml {
450     my $self = shift;
451     my $client = shift;
452     my $xml = OpenILS::Application::AppUtils->entityize(shift);
453
454     my $document = $parser->parse_string($xml);
455
456     my @mfr = $self->method_lookup("open-ils.ingest.flat_marc.biblio.xml")->run($document);
457     my @mXfe = $self->method_lookup("open-ils.ingest.extract.field_entry.all.xml")->run($document);
458     my ($fp) = $self->method_lookup("open-ils.ingest.fingerprint.xml")->run($xml);
459     my ($rd) = $self->method_lookup("open-ils.ingest.descriptor.xml")->run($xml);
460
461     return { full_rec => \@mfr, field_entries => \@mXfe, fingerprint => $fp, descriptor => $rd };
462 }
463 __PACKAGE__->register_method(  
464     api_name    => "open-ils.ingest.full.biblio.xml.readonly",
465     method        => "ro_biblio_ingest_single_xml",
466     api_level    => 1,
467     argc        => 1,
468 );                      
469
470 sub ro_biblio_ingest_single_record {
471     my $self = shift;
472     my $client = shift;
473     my $rec = shift;
474
475     OpenILS::Application::Ingest->post_init();
476     my $r = OpenSRF::AppSession
477             ->create('open-ils.cstore')
478             ->request( 'open-ils.cstore.direct.biblio.record_entry.retrieve' => $rec )
479             ->gather(1);
480
481     return undef unless ($r and @$r);
482
483     my ($res) = $self->method_lookup("open-ils.ingest.full.biblio.xml.readonly")->run($r->marc);
484
485     $_->source($rec) for (@{$res->{field_entries}});
486     $_->record($rec) for (@{$res->{full_rec}});
487     $res->{descriptor}->record($rec);
488
489     return $res;
490 }
491 __PACKAGE__->register_method(  
492     api_name    => "open-ils.ingest.full.biblio.record.readonly",
493     method        => "ro_biblio_ingest_single_record",
494     api_level    => 1,
495     argc        => 1,
496 );                      
497
498 sub ro_biblio_ingest_stream_record {
499     my $self = shift;
500     my $client = shift;
501
502     OpenILS::Application::Ingest->post_init();
503
504     my $ses = OpenSRF::AppSession->create('open-ils.cstore');
505
506     while (my ($resp) = $client->recv( count => 1, timeout => 5 )) {
507     
508         my $rec = $resp->content;
509         last unless (defined $rec);
510
511         $log->debug("Running open-ils.ingest.full.biblio.record.readonly ...");
512         my ($res) = $self->method_lookup("open-ils.ingest.full.biblio.record.readonly")->run($rec);
513
514         $_->source($rec) for (@{$res->{field_entries}});
515         $_->record($rec) for (@{$res->{full_rec}});
516
517         $client->respond( $res );
518     }
519
520     return undef;
521 }
522 __PACKAGE__->register_method(  
523     api_name    => "open-ils.ingest.full.biblio.record_stream.readonly",
524     method        => "ro_biblio_ingest_stream_record",
525     api_level    => 1,
526     stream        => 1,
527 );                      
528
529 sub ro_biblio_ingest_stream_xml {
530     my $self = shift;
531     my $client = shift;
532
533     OpenILS::Application::Ingest->post_init();
534
535     my $ses = OpenSRF::AppSession->create('open-ils.cstore');
536
537     while (my ($resp) = $client->recv( count => 1, timeout => 5 )) {
538     
539         my $xml = $resp->content;
540         last unless (defined $xml);
541
542         $log->debug("Running open-ils.ingest.full.biblio.xml.readonly ...");
543         my ($res) = $self->method_lookup("open-ils.ingest.full.biblio.xml.readonly")->run($xml);
544
545         $client->respond( $res );
546     }
547
548     return undef;
549 }
550 __PACKAGE__->register_method(  
551     api_name    => "open-ils.ingest.full.biblio.xml_stream.readonly",
552     method        => "ro_biblio_ingest_stream_xml",
553     api_level    => 1,
554     stream        => 1,
555 );                      
556
557 sub rw_biblio_ingest_stream_import {
558     my $self = shift;
559     my $client = shift;
560
561     OpenILS::Application::Ingest->post_init();
562
563     my $ses = OpenSRF::AppSession->create('open-ils.cstore');
564
565     while (my ($resp) = $client->recv( count => 1, timeout => 5 )) {
566     
567         my $bib = $resp->content;
568         last unless (defined $bib);
569
570         $log->debug("Running open-ils.ingest.full.biblio.xml.readonly ...");
571         my ($res) = $self->method_lookup("open-ils.ingest.full.biblio.xml.readonly")->run($bib->marc);
572
573         $_->source($bib->id) for (@{$res->{field_entries}});
574         $_->record($bib->id) for (@{$res->{full_rec}});
575
576         $client->respond( $res );
577     }
578
579     return undef;
580 }
581 __PACKAGE__->register_method(  
582     api_name    => "open-ils.ingest.full.biblio.bib_stream.import",
583     method        => "rw_biblio_ingest_stream_import",
584     api_level    => 1,
585     stream        => 1,
586 );                      
587
588
589 # --------------------------------------------------------------------------------
590 # Authority ingest
591
592 package OpenILS::Application::Ingest::Authority;
593 use base qw/OpenILS::Application::Ingest/;
594 use Unicode::Normalize;
595
596 sub rw_authority_ingest_single_object {
597     my $self = shift;
598     my $client = shift;
599     my $auth = shift;
600
601     my ($blob) = $self->method_lookup("open-ils.ingest.full.authority.object.readonly")->run($auth);
602     return undef unless ($blob);
603
604     my $cstore = OpenSRF::AppSession->connect('open-ils.cstore');
605
606     my $xact = $cstore->request('open-ils.cstore.transaction.begin')->gather(1);
607     my $tmp;
608
609     # update full_rec stuff ...
610     $tmp = $cstore->request(
611         'open-ils.cstore.direct.authority.full_rec.id_list.atomic',
612         { record => $auth->id }
613     )->gather(1);
614
615     $cstore->request( 'open-ils.cstore.direct.authority.full_rec.delete' => $_ )->gather(1) for (@$tmp);
616     $cstore->request( 'open-ils.cstore.direct.authority.full_rec.create' => $_ )->gather(1) for (@{ $blob->{full_rec} });
617
618     # XXX when we start extracting authority descriptors and adding sources ...
619     #
620     # update rec_descriptor stuff ...
621     #$tmp = $cstore->request(
622     #    'open-ils.cstore.direct.authority.record_descriptor.id_list.atomic',
623     #    { record => $auth->id }
624     #)->gather(1);
625     #
626     #$cstore->request( 'open-ils.cstore.direct.authority.record_descriptor.delete' => $_ )->gather(1) for (@$tmp);
627     #$cstore->request( 'open-ils.cstore.direct.authority.record_descriptor.create' => $blob->{descriptor} )->gather(1);
628     #$cstore->request( 'open-ils.cstore.direct.authority.record_entry.update' => $auth )->gather(1);
629
630     $cstore->request( 'open-ils.cstore.transaction.commit' )->gather(1) || return undef;;
631     $cstore->disconnect;
632
633     return $auth->id;
634 }
635 __PACKAGE__->register_method(  
636     api_name    => "open-ils.ingest.full.authority.object",
637     method        => "rw_authority_ingest_single_object",
638     api_level    => 1,
639     argc        => 1,
640 );                      
641
642 sub rw_authority_ingest_single_record {
643     my $self = shift;
644     my $client = shift;
645     my $rec = shift;
646
647     OpenILS::Application::Ingest->post_init();
648     my $cstore = OpenSRF::AppSession->connect( 'open-ils.cstore' );
649     $cstore->request('open-ils.cstore.transaction.begin')->gather(1);
650
651     my $r = $cstore->request( 'open-ils.cstore.direct.authority.record_entry.retrieve' => $rec )->gather(1);
652
653     $cstore->request('open-ils.cstore.transaction.rollback')->gather(1);
654     $cstore->disconnect;
655
656     return undef unless ($r and @$r);
657
658     return ($self->method_lookup("open-ils.ingest.full.authority.object")->run($r))[0];
659 }
660 __PACKAGE__->register_method(  
661     api_name    => "open-ils.ingest.full.authority.record",
662     method        => "rw_authority_ingest_single_record",
663     api_level    => 1,
664     argc        => 1,
665 );                      
666
667 sub ro_authority_ingest_single_object {
668     my $self = shift;
669     my $client = shift;
670     my $bib = shift;
671     my $xml = OpenILS::Application::AppUtils->entityize($bib->marc);
672
673     my $document = $parser->parse_string($xml);
674
675     my @mfr = $self->method_lookup("open-ils.ingest.flat_marc.authority.xml")->run($document);
676
677     $_->record($bib->id) for (@mfr);
678
679     return { full_rec => \@mfr };
680 }
681 __PACKAGE__->register_method(  
682     api_name    => "open-ils.ingest.full.authority.object.readonly",
683     method        => "ro_authority_ingest_single_object",
684     api_level    => 1,
685     argc        => 1,
686 );                      
687 __PACKAGE__->register_method(  
688     api_name    => "open-ils.ingest.full.serial.object.readonly",
689     method        => "ro_authority_ingest_single_object",
690     api_level    => 1,
691     argc        => 1,
692 );                      
693
694
695 sub ro_authority_ingest_single_xml {
696     my $self = shift;
697     my $client = shift;
698     my $xml = OpenILS::Application::AppUtils->entityize(shift);
699
700     my $document = $parser->parse_string($xml);
701
702     my @mfr = $self->method_lookup("open-ils.ingest.flat_marc.authority.xml")->run($document);
703
704     return { full_rec => \@mfr };
705 }
706 __PACKAGE__->register_method(  
707     api_name    => "open-ils.ingest.full.authority.xml.readonly",
708     method        => "ro_authority_ingest_single_xml",
709     api_level    => 1,
710     argc        => 1,
711 );                      
712
713 sub ro_authority_ingest_single_record {
714     my $self = shift;
715     my $client = shift;
716     my $rec = shift;
717
718     OpenILS::Application::Ingest->post_init();
719     my $r = OpenSRF::AppSession
720             ->create('open-ils.cstore')
721             ->request( 'open-ils.cstore.direct.authority.record_entry.retrieve' => $rec )
722             ->gather(1);
723
724     return undef unless ($r and @$r);
725
726     my ($res) = $self->method_lookup("open-ils.ingest.full.authority.xml.readonly")->run($r->marc);
727
728     $_->record($rec) for (@{$res->{full_rec}});
729     $res->{descriptor}->record($rec);
730
731     return $res;
732 }
733 __PACKAGE__->register_method(  
734     api_name    => "open-ils.ingest.full.authority.record.readonly",
735     method        => "ro_authority_ingest_single_record",
736     api_level    => 1,
737     argc        => 1,
738 );                      
739
740 sub ro_authority_ingest_stream_record {
741     my $self = shift;
742     my $client = shift;
743
744     OpenILS::Application::Ingest->post_init();
745
746     my $ses = OpenSRF::AppSession->create('open-ils.cstore');
747
748     while (my ($resp) = $client->recv( count => 1, timeout => 5 )) {
749     
750         my $rec = $resp->content;
751         last unless (defined $rec);
752
753         $log->debug("Running open-ils.ingest.full.authority.record.readonly ...");
754         my ($res) = $self->method_lookup("open-ils.ingest.full.authority.record.readonly")->run($rec);
755
756         $_->record($rec) for (@{$res->{full_rec}});
757
758         $client->respond( $res );
759     }
760
761     return undef;
762 }
763 __PACKAGE__->register_method(  
764     api_name    => "open-ils.ingest.full.authority.record_stream.readonly",
765     method        => "ro_authority_ingest_stream_record",
766     api_level    => 1,
767     stream        => 1,
768 );                      
769
770 sub ro_authority_ingest_stream_xml {
771     my $self = shift;
772     my $client = shift;
773
774     OpenILS::Application::Ingest->post_init();
775
776     my $ses = OpenSRF::AppSession->create('open-ils.cstore');
777
778     while (my ($resp) = $client->recv( count => 1, timeout => 5 )) {
779     
780         my $xml = $resp->content;
781         last unless (defined $xml);
782
783         $log->debug("Running open-ils.ingest.full.authority.xml.readonly ...");
784         my ($res) = $self->method_lookup("open-ils.ingest.full.authority.xml.readonly")->run($xml);
785
786         $client->respond( $res );
787     }
788
789     return undef;
790 }
791 __PACKAGE__->register_method(  
792     api_name    => "open-ils.ingest.full.authority.xml_stream.readonly",
793     method        => "ro_authority_ingest_stream_xml",
794     api_level    => 1,
795     stream        => 1,
796 );                      
797
798 sub rw_authority_ingest_stream_import {
799     my $self = shift;
800     my $client = shift;
801
802     OpenILS::Application::Ingest->post_init();
803
804     my $ses = OpenSRF::AppSession->create('open-ils.cstore');
805
806     while (my ($resp) = $client->recv( count => 1, timeout => 5 )) {
807     
808         my $bib = $resp->content;
809         last unless (defined $bib);
810
811         $log->debug("Running open-ils.ingest.full.authority.xml.readonly ...");
812         my ($res) = $self->method_lookup("open-ils.ingest.full.authority.xml.readonly")->run($bib->marc);
813
814         $_->record($bib->id) for (@{$res->{full_rec}});
815
816         $client->respond( $res );
817     }
818
819     return undef;
820 }
821 __PACKAGE__->register_method(  
822     api_name    => "open-ils.ingest.full.authority.bib_stream.import",
823     method        => "rw_authority_ingest_stream_import",
824     api_level    => 1,
825     stream        => 1,
826 );                      
827
828 # --------------------------------------------------------------------------------
829 # Serial ingest
830
831 package OpenILS::Application::Ingest::Serial;
832 use base qw/OpenILS::Application::Ingest/;
833 use Unicode::Normalize;
834
835 sub ro_serial_ingest_single_object {
836     my $self = shift;
837     my $client = shift;
838     my $bib = shift;
839     my $xml = OpenILS::Application::AppUtils->entityize($bib->marc);
840
841     my $document = $parser->parse_string($xml);
842
843     my @mfr = $self->method_lookup("open-ils.ingest.flat_marc.serial.xml")->run($document);
844
845     $_->record($bib->id) for (@mfr);
846
847     return { full_rec => \@mfr };
848 }
849 __PACKAGE__->register_method(  
850     api_name    => "open-ils.ingest.full.serial.object.readonly",
851     method        => "ro_serial_ingest_single_object",
852     api_level    => 1,
853     argc        => 1,
854 );                      
855
856 sub ro_serial_ingest_single_xml {
857     my $self = shift;
858     my $client = shift;
859     my $xml = OpenILS::Application::AppUtils->entityize(shift);
860
861     my $document = $parser->parse_string($xml);
862
863     my @mfr = $self->method_lookup("open-ils.ingest.flat_marc.serial.xml")->run($document);
864
865     return { full_rec => \@mfr };
866 }
867 __PACKAGE__->register_method(  
868     api_name    => "open-ils.ingest.full.serial.xml.readonly",
869     method        => "ro_serial_ingest_single_xml",
870     api_level    => 1,
871     argc        => 1,
872 );                      
873
874 sub ro_serial_ingest_single_record {
875     my $self = shift;
876     my $client = shift;
877     my $rec = shift;
878
879     OpenILS::Application::Ingest->post_init();
880     my $r = OpenSRF::AppSession
881             ->create('open-ils.cstore')
882             ->request( 'open-ils.cstore.direct.serial.record_entry.retrieve' => $rec )
883             ->gather(1);
884
885     return undef unless ($r and @$r);
886
887     my ($res) = $self->method_lookup("open-ils.ingest.full.serial.xml.readonly")->run($r->marc);
888
889     $_->record($rec) for (@{$res->{full_rec}});
890     $res->{descriptor}->record($rec);
891
892     return $res;
893 }
894 __PACKAGE__->register_method(  
895     api_name    => "open-ils.ingest.full.serial.record.readonly",
896     method        => "ro_serial_ingest_single_record",
897     api_level    => 1,
898     argc        => 1,
899 );                      
900
901 sub ro_serial_ingest_stream_record {
902     my $self = shift;
903     my $client = shift;
904
905     OpenILS::Application::Ingest->post_init();
906
907     my $ses = OpenSRF::AppSession->create('open-ils.cstore');
908
909     while (my ($resp) = $client->recv( count => 1, timeout => 5 )) {
910     
911         my $rec = $resp->content;
912         last unless (defined $rec);
913
914         $log->debug("Running open-ils.ingest.full.serial.record.readonly ...");
915         my ($res) = $self->method_lookup("open-ils.ingest.full.serial.record.readonly")->run($rec);
916
917         $_->record($rec) for (@{$res->{full_rec}});
918
919         $client->respond( $res );
920     }
921
922     return undef;
923 }
924 __PACKAGE__->register_method(  
925     api_name    => "open-ils.ingest.full.serial.record_stream.readonly",
926     method        => "ro_serial_ingest_stream_record",
927     api_level    => 1,
928     stream        => 1,
929 );                      
930
931 sub ro_serial_ingest_stream_xml {
932     my $self = shift;
933     my $client = shift;
934
935     OpenILS::Application::Ingest->post_init();
936
937     my $ses = OpenSRF::AppSession->create('open-ils.cstore');
938
939     while (my ($resp) = $client->recv( count => 1, timeout => 5 )) {
940     
941         my $xml = $resp->content;
942         last unless (defined $xml);
943
944         $log->debug("Running open-ils.ingest.full.serial.xml.readonly ...");
945         my ($res) = $self->method_lookup("open-ils.ingest.full.serial.xml.readonly")->run($xml);
946
947         $client->respond( $res );
948     }
949
950     return undef;
951 }
952 __PACKAGE__->register_method(  
953     api_name    => "open-ils.ingest.full.serial.xml_stream.readonly",
954     method        => "ro_serial_ingest_stream_xml",
955     api_level    => 1,
956     stream        => 1,
957 );                      
958
959 sub rw_serial_ingest_stream_import {
960     my $self = shift;
961     my $client = shift;
962
963     OpenILS::Application::Ingest->post_init();
964
965     my $ses = OpenSRF::AppSession->create('open-ils.cstore');
966
967     while (my ($resp) = $client->recv( count => 1, timeout => 5 )) {
968     
969         my $bib = $resp->content;
970         last unless (defined $bib);
971
972         $log->debug("Running open-ils.ingest.full.serial.xml.readonly ...");
973         my ($res) = $self->method_lookup("open-ils.ingest.full.serial.xml.readonly")->run($bib->marc);
974
975         $_->record($bib->id) for (@{$res->{full_rec}});
976
977         $client->respond( $res );
978     }
979
980     return undef;
981 }
982 __PACKAGE__->register_method(  
983     api_name    => "open-ils.ingest.full.serial.bib_stream.import",
984     method        => "rw_serial_ingest_stream_import",
985     api_level    => 1,
986     stream        => 1,
987 );                      
988
989
990 # --------------------------------------------------------------------------------
991 # MARC index extraction
992
993 package OpenILS::Application::Ingest::XPATH;
994 use base qw/OpenILS::Application::Ingest/;
995 use Unicode::Normalize;
996
997 # give this an XML documentElement and an XPATH expression
998 sub xpath_to_string {
999     my $xml = shift;
1000     my $xpath = shift;
1001     my $ns_uri = shift;
1002     my $ns_prefix = shift;
1003     my $unique = shift;
1004
1005     $xml->setNamespace( $ns_uri, $ns_prefix, 1 ) if ($ns_uri && $ns_prefix);
1006
1007     my $string = "";
1008
1009     # grab the set of matching nodes
1010     my @nodes = $xml->findnodes( $xpath );
1011     for my $value (@nodes) {
1012
1013         # grab all children of the node
1014         my @children = $value->childNodes();
1015         for my $child (@children) {
1016
1017             # add the childs content to the growing buffer
1018             my $content = quotemeta($child->textContent);
1019             next if ($unique && $string =~ /$content/);  # uniquify the values
1020             $string .= $child->textContent . " ";
1021         }
1022         if( ! @children ) {
1023             $string .= $value->textContent . " ";
1024         }
1025     }
1026
1027     $string =~ s/(\w+)\/(\w+)/$1 $2/sgo;
1028     $string =~ s/(\d{4})-(\d{4})/$1 $2/sgo;
1029
1030     return NFD($string);
1031 }
1032
1033 sub class_index_string_xml {
1034     my $self = shift;
1035     my $client = shift;
1036     my $xml = shift;
1037     my @classes = @_;
1038
1039     OpenILS::Application::Ingest->post_init();
1040     $xml = $parser->parse_string(OpenILS::Application::AppUtils->entityize($xml)) unless (ref $xml);
1041
1042     my %transform_cache;
1043     
1044     for my $class (@classes) {
1045         my $class_constructor = "Fieldmapper::metabib::${class}_field_entry";
1046         for my $type ( keys %{ $xpathset->{$class} } ) {
1047
1048             my $def = $xpathset->{$class}->{$type};
1049             my $sf = $OpenILS::Application::Ingest::supported_formats{$def->{format}};
1050
1051             my $document = $xml;
1052
1053             if ($sf->{xslt}) {
1054                 $document = $transform_cache{$def->{format}} || $sf->{xslt}->transform($xml);
1055                 $transform_cache{$def->{format}} = $document;
1056             }
1057
1058             my $value =  xpath_to_string(
1059                     $document->documentElement    => $def->{xpath},
1060                     $sf->{ns}            => $def->{format},
1061                     1
1062             );
1063
1064             next unless $value;
1065
1066             $value = NFD($value);
1067             $value =~ s/\pM+//sgo;
1068             $value =~ s/\pC+//sgo;
1069             $value =~ s/\W+$//sgo;
1070
1071             $value =~ s/\b\.+\b//sgo;
1072             $value = lc($value);
1073
1074             my $fm = $class_constructor->new;
1075             $fm->value( $value );
1076             $fm->field( $xpathset->{$class}->{$type}->{id} );
1077             $client->respond($fm);
1078         }
1079     }
1080     return undef;
1081 }
1082 __PACKAGE__->register_method(  
1083     api_name    => "open-ils.ingest.field_entry.class.xml",
1084     method        => "class_index_string_xml",
1085     api_level    => 1,
1086     argc        => 2,
1087     stream        => 1,
1088 );                      
1089
1090 sub class_index_string_record {
1091     my $self = shift;
1092     my $client = shift;
1093     my $rec = shift;
1094     my @classes = shift;
1095
1096     OpenILS::Application::Ingest->post_init();
1097     my $r = OpenSRF::AppSession
1098             ->create('open-ils.cstore')
1099             ->request( 'open-ils.cstore.direct.authority.record_entry.retrieve' => $rec )
1100             ->gather(1);
1101
1102     return undef unless ($r and @$r);
1103
1104     for my $fm ($self->method_lookup("open-ils.ingest.field_entry.class.xml")->run($r->marc, @classes)) {
1105         $fm->source($rec);
1106         $client->respond($fm);
1107     }
1108     return undef;
1109 }
1110 __PACKAGE__->register_method(  
1111     api_name    => "open-ils.ingest.field_entry.class.record",
1112     method        => "class_index_string_record",
1113     api_level    => 1,
1114     argc        => 2,
1115     stream        => 1,
1116 );                      
1117
1118 sub all_index_string_xml {
1119     my $self = shift;
1120     my $client = shift;
1121     my $xml = shift;
1122
1123     for my $fm ($self->method_lookup("open-ils.ingest.field_entry.class.xml")->run($xml, keys(%$xpathset))) {
1124         $client->respond($fm);
1125     }
1126     return undef;
1127 }
1128 __PACKAGE__->register_method(  
1129     api_name    => "open-ils.ingest.extract.field_entry.all.xml",
1130     method        => "all_index_string_xml",
1131     api_level    => 1,
1132     argc        => 1,
1133     stream        => 1,
1134 );                      
1135
1136 sub all_index_string_record {
1137     my $self = shift;
1138     my $client = shift;
1139     my $rec = shift;
1140
1141     OpenILS::Application::Ingest->post_init();
1142     my $r = OpenSRF::AppSession
1143             ->create('open-ils.cstore')
1144             ->request( 'open-ils.cstore.direct.biblio.record_entry.retrieve' => $rec )
1145             ->gather(1);
1146
1147     return undef unless ($r and @$r);
1148
1149     for my $fm ($self->method_lookup("open-ils.ingest.field_entry.class.xml")->run($r->marc, keys(%$xpathset))) {
1150         $fm->source($rec);
1151         $client->respond($fm);
1152     }
1153     return undef;
1154 }
1155 __PACKAGE__->register_method(  
1156     api_name    => "open-ils.ingest.extract.field_entry.all.record",
1157     method        => "all_index_string_record",
1158     api_level    => 1,
1159     argc        => 1,
1160     stream        => 1,
1161 );                      
1162
1163 # --------------------------------------------------------------------------------
1164 # Flat MARC
1165
1166 package OpenILS::Application::Ingest::FlatMARC;
1167 use base qw/OpenILS::Application::Ingest/;
1168 use Unicode::Normalize;
1169
1170
1171 sub _marcxml_to_full_rows {
1172
1173     my $marcxml = shift;
1174     my $xmltype = shift || 'metabib';
1175
1176     my $type = "Fieldmapper::${xmltype}::full_rec";
1177
1178     my @ns_list;
1179     
1180     my ($root) = $marcxml->findnodes('//*[local-name()="record"]');
1181
1182     for my $tagline ( @{$root->getChildrenByTagName("leader")} ) {
1183         next unless $tagline;
1184
1185         my $ns = $type->new;
1186
1187         $ns->tag( 'LDR' );
1188         my $val = $tagline->textContent;
1189         $val = NFD($val);
1190         $val =~ s/\pM+//sgo;
1191         $val =~ s/\pC+//sgo;
1192         $val =~ s/\W+$//sgo;
1193         $ns->value( $val );
1194
1195         push @ns_list, $ns;
1196     }
1197
1198     for my $tagline ( @{$root->getChildrenByTagName("controlfield")} ) {
1199         next unless $tagline;
1200
1201         my $ns = $type->new;
1202
1203         $ns->tag( $tagline->getAttribute( "tag" ) );
1204         my $val = $tagline->textContent;
1205         $val = NFD($val);
1206         $val =~ s/\pM+//sgo;
1207         $val =~ s/\pC+//sgo;
1208         $val =~ s/\W+$//sgo;
1209         $ns->value( $val );
1210
1211         push @ns_list, $ns;
1212     }
1213
1214     for my $tagline ( @{$root->getChildrenByTagName("datafield")} ) {
1215         next unless $tagline;
1216
1217         my $tag = $tagline->getAttribute( "tag" );
1218         my $ind1 = $tagline->getAttribute( "ind1" );
1219         my $ind2 = $tagline->getAttribute( "ind2" );
1220
1221         for my $data ( @{$tagline->getChildrenByTagName('subfield')} ) {
1222             next unless $data;
1223
1224             my $ns = $type->new;
1225
1226             $ns->tag( $tag );
1227             $ns->ind1( $ind1 );
1228             $ns->ind2( $ind2 );
1229             $ns->subfield( $data->getAttribute( "code" ) );
1230             my $val = $data->textContent;
1231             $val = NFD($val);
1232             $val =~ s/\pM+//sgo;
1233             $val =~ s/\pC+//sgo;
1234             $val =~ s/\W+$//sgo;
1235             $val =~ s/(\d{4})-(\d{4})/$1 $2/sgo;
1236             $val =~ s/(\w+)\/(\w+)/$1 $2/sgo;
1237             $ns->value( lc($val) );
1238
1239             push @ns_list, $ns;
1240         }
1241
1242         if ($xmltype eq 'metabib' and $tag eq '245') {
1243                $tag = 'tnf';
1244     
1245             for my $data ( @{$tagline->getChildrenByTagName('subfield')} ) {
1246                 next unless ($data and $data->getAttribute( "code" ) eq 'a');
1247     
1248                 $ns = $type->new;
1249     
1250                 $ns->tag( $tag );
1251                 $ns->ind1( $ind1 );
1252                 $ns->ind2( $ind2 );
1253                 $ns->subfield( $data->getAttribute( "code" ) );
1254                 my $val = substr( $data->textContent, $ind2 );
1255                 $val = NFD($val);
1256                 $val =~ s/\pM+//sgo;
1257                 $val =~ s/\pC+//sgo;
1258                 $val =~ s/\W+$//sgo;
1259                 $val =~ s/(\w+)\/(\w+)/$1 $2/sgo;
1260                 $val =~ s/(\d{4})-(\d{4})/$1 $2/sgo;
1261                 $ns->value( lc($val) );
1262     
1263                 push @ns_list, $ns;
1264             }
1265         }
1266     }
1267
1268     $log->debug("Returning ".scalar(@ns_list)." Fieldmapper nodes from $xmltype xml");
1269     return @ns_list;
1270 }
1271
1272 sub flat_marc_xml {
1273     my $self = shift;
1274     my $client = shift;
1275     my $xml = shift;
1276
1277     $log->debug("processing [$xml]");
1278
1279     $xml = $parser->parse_string(OpenILS::Application::AppUtils->entityize($xml)) unless (ref $xml);
1280
1281     my $type = 'metabib';
1282     $type = 'authority' if ($self->api_name =~ /authority/o);
1283     $type = 'serial' if ($self->api_name =~ /serial/o);
1284
1285     OpenILS::Application::Ingest->post_init();
1286
1287     $client->respond($_) for (_marcxml_to_full_rows($xml, $type));
1288     return undef;
1289 }
1290 __PACKAGE__->register_method(  
1291     api_name    => "open-ils.ingest.flat_marc.authority.xml",
1292     method        => "flat_marc_xml",
1293     api_level    => 1,
1294     argc        => 1,
1295     stream        => 1,
1296 );                      
1297 __PACKAGE__->register_method(  
1298     api_name    => "open-ils.ingest.flat_marc.biblio.xml",
1299     method        => "flat_marc_xml",
1300     api_level    => 1,
1301     argc        => 1,
1302     stream        => 1,
1303 );                      
1304 __PACKAGE__->register_method(  
1305     api_name    => "open-ils.ingest.flat_marc.serial.xml",
1306     method        => "flat_marc_xml",
1307     api_level    => 1,
1308     argc        => 1,
1309     stream        => 1,
1310 );                      
1311
1312 sub flat_marc_record {
1313     my $self = shift;
1314     my $client = shift;
1315     my $rec = shift;
1316
1317     my $type = 'biblio';
1318     $type = 'authority' if ($self->api_name =~ /authority/o);
1319     $type = 'serial' if ($self->api_name =~ /serial/o);
1320
1321     OpenILS::Application::Ingest->post_init();
1322     my $r = OpenSRF::AppSession
1323             ->create('open-ils.cstore')
1324             ->request( "open-ils.cstore.direct.${type}.record_entry.retrieve" => $rec )
1325             ->gather(1);
1326
1327
1328     return undef unless ($r and $r->marc);
1329
1330     my @rows = $self->method_lookup("open-ils.ingest.flat_marc.$type.xml")->run($r->marc);
1331     for my $row (@rows) {
1332         $client->respond($row);
1333         $log->debug(OpenSRF::Utils::JSON->perl2JSON($row), DEBUG);
1334     }
1335     return undef;
1336 }
1337 __PACKAGE__->register_method(  
1338     api_name    => "open-ils.ingest.flat_marc.biblio.record_entry",
1339     method        => "flat_marc_record",
1340     api_level    => 1,
1341     argc        => 1,
1342     stream        => 1,
1343 );                      
1344 __PACKAGE__->register_method(  
1345     api_name    => "open-ils.ingest.flat_marc.authority.record_entry",
1346     method        => "flat_marc_record",
1347     api_level    => 1,
1348     argc        => 1,
1349     stream        => 1,
1350 );                      
1351 __PACKAGE__->register_method(  
1352     api_name    => "open-ils.ingest.flat_marc.serial.record_entry",
1353     method        => "flat_marc_record",
1354     api_level    => 1,
1355     argc        => 1,
1356     stream        => 1,
1357 );                      
1358
1359 # --------------------------------------------------------------------------------
1360 # URI extraction
1361
1362 package OpenILS::Application::Ingest::Biblio::URI;
1363 use base qw/OpenILS::Application::Ingest/;
1364 use Unicode::Normalize;
1365 use OpenSRF::EX qw/:try/;
1366
1367
1368 sub _extract_856_uris {
1369
1370     my $rec   = shift;
1371     my $max_cn = shift;
1372     my $max_uri = shift;
1373     my @objects;
1374     
1375     my $recid = $rec->id;
1376     my $marcxml = $rec->marc;
1377
1378     my $document = $parser->parse_string($marcxml);
1379     my @nodes = $document->findnodes('//*[local-name()="datafield" and @tag="856" and (@ind1="4" or @ind1="1") and (@ind2="0" or @ind2="1")]');
1380
1381     my $cstore = OpenSRF::AppSession->connect('open-ils.cstore');
1382
1383     my %cn_cache;
1384
1385     for my $node (@nodes) {
1386         # first, is there a URI?
1387         my $href = $node->findvalue('*[local-name()="subfield" and @code="u"]/text()');
1388         next unless ($href);
1389
1390         # now, find the best possible label
1391         my $label = $node->findvalue('*[local-name()="subfield" and @code="y"]/text()');
1392         $label ||= $node->findvalue('*[local-name()="subfield" and @code="3"]/text()');
1393         $label ||= $href;
1394
1395         # look for use info
1396         my $use = $node->findvalue('*[local-name()="subfield" and @code="z"]/text()');
1397         $use ||= $node->findvalue('*[local-name()="subfield" and @code="2"]/text()');
1398         $use ||= $node->findvalue('*[local-name()="subfield" and @code="n"]/text()');
1399
1400         # moving on to the URI owner
1401         my $owner = $node->findvalue('*[local-name()="subfield" and @code="9"]/text()'); # Evergreen special sauce
1402         $owner ||= $node->findvalue('*[local-name()="subfield" and @code="w"]/text()');
1403         $owner ||= $node->findvalue('*[local-name()="subfield" and @code="n"]/text()');
1404
1405         $owner =~ s/^.*?\((\w+)\).*$/$1/o; # unwrap first paren-enclosed string and then ...
1406
1407         # no owner? skip it :(
1408         next unless ($owner);
1409
1410         my $org = $cstore
1411             ->request( 'open-ils.cstore.direct.actor.org_unit.search' => { shortname => $owner} )
1412             ->gather(1);
1413
1414         next unless ($org);
1415
1416         # now we can construct the uri object
1417         my $uri = $cstore
1418             ->request( 'open-ils.cstore.direct.asset.uri.search' => { label => $label, href => $href, use_restriction => $use, active => 't' } )
1419             ->gather(1);
1420
1421         if (!$uri) {
1422             $uri = Fieldmapper::asset::uri->new;
1423             $uri->isnew( 1 );
1424             $uri->id( $$max_uri++ );
1425             $uri->label($label);
1426             $uri->href($href);
1427             $uri->active('t');
1428             $uri->use_restriction($use);
1429         }
1430
1431         # see if we need to create a call number
1432         my $cn = $cn_cache{$org->id};
1433         $cn = $cn->clone if ($cn);
1434         $cn->clear_isnew if ($cn);
1435
1436         $cn ||= $cstore
1437             ->request( 'open-ils.cstore.direct.asset.call_number.search' => { owning_lib => $org->id, record => $recid, label => '##URI##' } )
1438             ->gather(1);
1439
1440         if (!$cn) {
1441             $cn = Fieldmapper::asset::call_number->new;
1442             $cn->isnew( 1 );
1443             $cn->deleted('f');
1444             $cn->id( $$max_cn++ );
1445             $cn->owning_lib( $org->id );
1446             $cn->record( $recid );
1447             $cn->create_date( 'now' );
1448             $cn->creator( $rec->creator );
1449             $cn->editor( $rec->editor );
1450             $cn->edit_date( 'now' );
1451             $cn->label( '##URI##' );
1452         }
1453
1454         $cn_cache{$org->id} = $cn;
1455
1456         push @objects, { uri => $uri, call_number => $cn };
1457     }
1458
1459     $log->debug("Returning ".scalar(@objects)." URI nodes for record $recid");
1460     $cstore->disconnect;
1461     return @objects;
1462 }
1463
1464 sub get_uris_record {
1465     my $self = shift;
1466     my $client = shift;
1467     my $rec = shift;
1468
1469     OpenILS::Application::Ingest->post_init();
1470     my $r = OpenSRF::AppSession
1471             ->create('open-ils.cstore')
1472             ->request( "open-ils.cstore.direct.biblio.record_entry.retrieve" => $rec )
1473             ->gather(1);
1474
1475     return undef unless ($r and $r->marc);
1476
1477     $client->respond($_) for (_extract_856_uris($r));
1478     return undef;
1479 }
1480 __PACKAGE__->register_method(  
1481     api_name    => "open-ils.ingest.856_uri.record",
1482     method        => "get_uris_record",
1483     api_level    => 1,
1484     argc        => 1,
1485     stream        => 1,
1486 );                      
1487
1488 sub get_uris_object {
1489     my $self = shift;
1490     my $client = shift;
1491     my $obj = shift;
1492     my $max_cn = shift;
1493     my $max_uri = shift;
1494
1495     return undef unless ($obj and $obj->marc);
1496
1497     $client->respond($_) for (_extract_856_uris($obj, \$max_cn, \$max_uri));
1498     return undef;
1499 }
1500 __PACKAGE__->register_method(  
1501     api_name    => "open-ils.ingest.856_uri.object",
1502     method        => "get_uris_object",
1503     api_level    => 1,
1504     argc        => 1,
1505     stream        => 1,
1506 );                      
1507
1508
1509 # --------------------------------------------------------------------------------
1510 # Fingerprinting
1511
1512 package OpenILS::Application::Ingest::Biblio::Fingerprint;
1513 use base qw/OpenILS::Application::Ingest/;
1514 use Unicode::Normalize;
1515 use OpenSRF::EX qw/:try/;
1516
1517 sub biblio_fingerprint_record {
1518     my $self = shift;
1519     my $client = shift;
1520     my $rec = shift;
1521
1522     OpenILS::Application::Ingest->post_init();
1523
1524     my $r = OpenSRF::AppSession
1525             ->create('open-ils.cstore')
1526             ->request( 'open-ils.cstore.direct.biblio.record_entry.retrieve' => $rec )
1527             ->gather(1);
1528
1529     return undef unless ($r and $r->marc);
1530
1531     my ($fp) = $self->method_lookup('open-ils.ingest.fingerprint.xml')->run($r->marc);
1532     $log->debug("Returning [$fp] as fingerprint for record $rec", INFO);
1533     $fp->{quality} = int($fp->{quality});
1534     return $fp;
1535 }
1536 __PACKAGE__->register_method(  
1537     api_name    => "open-ils.ingest.fingerprint.record",
1538     method        => "biblio_fingerprint_record",
1539     api_level    => 1,
1540     argc        => 1,
1541 );                      
1542
1543 our $fp_script;
1544 sub biblio_fingerprint {
1545     my $self = shift;
1546     my $client = shift;
1547     my $xml = OpenILS::Application::AppUtils->entityize(shift);
1548
1549     $log->internal("Got MARC [$xml]");
1550
1551     if(!$fp_script) {
1552         my @pfx = ( "apps", "open-ils.ingest","app_settings" );
1553         my $conf = OpenSRF::Utils::SettingsClient->new;
1554
1555         my $libs        = $conf->config_value(@pfx, 'script_path');
1556         my $script_file = $conf->config_value(@pfx, 'scripts', 'biblio_fingerprint');
1557         my $script_libs = (ref($libs)) ? $libs : [$libs];
1558
1559         $log->debug("Loading script $script_file for biblio fingerprinting...");
1560         
1561         $fp_script = new OpenILS::Utils::ScriptRunner
1562             ( file        => $script_file,
1563               paths        => $script_libs,
1564               reset_count    => 100 );
1565     }
1566
1567     $fp_script->insert('environment' => {marc => $xml} => 1);
1568
1569     my $res = $fp_script->run || ($log->error( "Fingerprint script died!  $@" ) && return undef);
1570     $log->debug("Script for biblio fingerprinting completed successfully...");
1571
1572     return $res;
1573 }
1574 __PACKAGE__->register_method(  
1575     api_name    => "open-ils.ingest.fingerprint.xml",
1576     method        => "biblio_fingerprint",
1577     api_level    => 1,
1578     argc        => 1,
1579 );                      
1580
1581 our $rd_script;
1582 sub biblio_descriptor {
1583     my $self = shift;
1584     my $client = shift;
1585     my $xml = OpenILS::Application::AppUtils->entityize(shift);
1586
1587     $log->internal("Got MARC [$xml]");
1588
1589     if(!$rd_script) {
1590         my @pfx = ( "apps", "open-ils.ingest","app_settings" );
1591         my $conf = OpenSRF::Utils::SettingsClient->new;
1592
1593         my $libs        = $conf->config_value(@pfx, 'script_path');
1594         my $script_file = $conf->config_value(@pfx, 'scripts', 'biblio_descriptor');
1595         my $script_libs = (ref($libs)) ? $libs : [$libs];
1596
1597         $log->debug("Loading script $script_file for biblio descriptor extraction...");
1598         
1599         $rd_script = new OpenILS::Utils::ScriptRunner
1600             ( file        => $script_file,
1601               paths        => $script_libs,
1602               reset_count    => 100 );
1603     }
1604
1605     $log->debug("Setting up environment for descriptor extraction script...");
1606     $rd_script->insert('environment.marc' => $xml => 1);
1607     $log->debug("Environment building complete...");
1608
1609     my $res = $rd_script->run || ($log->error( "Descriptor script died!  $@" ) && return undef);
1610     $log->debug("Script for biblio descriptor extraction completed successfully");
1611
1612     my $d1 = $res->date1;
1613     if ($d1 && $d1 ne '    ') {
1614         $d1 =~ tr/ux/00/;
1615         $res->date1( $d1 );
1616     }
1617
1618     my $d2 = $res->date2;
1619     if ($d2 && $d2 ne '    ') {
1620         $d2 =~ tr/ux/99/;
1621         $res->date2( $d2 );
1622     }
1623
1624     return $res;
1625 }
1626 __PACKAGE__->register_method(  
1627     api_name    => "open-ils.ingest.descriptor.xml",
1628     method        => "biblio_descriptor",
1629     api_level    => 1,
1630     argc        => 1,
1631 );                      
1632
1633
1634 1;
1635
1636 # vim:et:ts=4:sw=4: