]> git.evergreen-ils.org Git - working/Evergreen.git/blob - Open-ILS/src/perlmods/OpenILS/Application/Ingest.pm
Protect against duplicate Located URIs at ingest time
[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.transaction.commit' )->gather(1) || return undef;;
337     $cstore->disconnect;
338
339     return $bib->id;
340 }
341 __PACKAGE__->register_method(  
342     api_name    => "open-ils.ingest.full.biblio.object",
343     method        => "rw_biblio_ingest_single_object",
344     api_level    => 1,
345     argc        => 1,
346 );                      
347
348 sub rw_biblio_ingest_single_record {
349     my $self = shift;
350     my $client = shift;
351     my $rec = shift;
352
353     OpenILS::Application::Ingest->post_init();
354     my $cstore = OpenSRF::AppSession->connect( 'open-ils.cstore' );
355     $cstore->request('open-ils.cstore.transaction.begin')->gather(1);
356
357     my $r = $cstore->request( 'open-ils.cstore.direct.biblio.record_entry.retrieve' => $rec )->gather(1);
358
359     $cstore->request('open-ils.cstore.transaction.rollback')->gather(1);
360     $cstore->disconnect;
361
362     return undef unless ($r and @$r);
363
364     return ($self->method_lookup("open-ils.ingest.full.biblio.object")->run($r))[0];
365 }
366 __PACKAGE__->register_method(  
367     api_name    => "open-ils.ingest.full.biblio.record",
368     method        => "rw_biblio_ingest_single_record",
369     api_level    => 1,
370     argc        => 1,
371 );                      
372
373 sub rw_biblio_ingest_record_list {
374     my $self = shift;
375     my $client = shift;
376     my @rec = ref($_[0]) ? @{ $_[0] } : @_ ;
377
378     OpenILS::Application::Ingest->post_init();
379     my $cstore = OpenSRF::AppSession->connect( 'open-ils.cstore' );
380     $cstore->request('open-ils.cstore.transaction.begin')->gather(1);
381
382     my $r = $cstore->request( 'open-ils.cstore.direct.biblio.record_entry.search.atomic' => { id => \@rec } )->gather(1);
383
384     $cstore->request('open-ils.cstore.transaction.rollback')->gather(1);
385     $cstore->disconnect;
386
387     return undef unless ($r and @$r);
388
389     my $count = 0;
390     for (@$r) {
391         if (($self->method_lookup("open-ils.ingest.full.biblio.object")->run($_))[0]) {
392             $count++
393         }
394     }
395     return $count;
396 }
397 __PACKAGE__->register_method(  
398     api_name    => "open-ils.ingest.full.biblio.record_list",
399     method        => "rw_biblio_ingest_record_list",
400     api_level    => 1,
401     argc        => 1,
402 );                      
403
404 sub ro_biblio_ingest_single_object {
405     my $self = shift;
406     my $client = shift;
407     my $bib = shift;
408     my $xml = OpenILS::Application::AppUtils->entityize($bib->marc);
409     my $max_cn = shift;
410     my $max_uri = shift;
411
412     my $cstore = OpenSRF::AppSession->connect( 'open-ils.cstore' );
413
414     if (!$max_cn) {
415         my $cn = $cstore->request( 'open-ils.cstore.direct.asset.call_number.search' => { id => { '!=' => undef } }, { limit => 1, order_by => { acn => 'id desc' } } )->gather(1);
416         $max_cn = int($cn->id) + 1000;
417     }
418
419     if (!$max_uri) {
420         my $cn = $cstore->request( 'open-ils.cstore.direct.asset.call_number.search' => { id => { '!=' => undef } }, { limit => 1, order_by => { acn => 'id desc' } } )->gather(1);
421         $max_uri = int($cn->id) + 1000;
422     }
423
424     $cstore->disconnect;
425
426     my $document = $parser->parse_string($xml);
427
428     my @uris = $self->method_lookup("open-ils.ingest.856_uri.object")->run($bib, $max_cn, $max_uri);
429     my @mfr = $self->method_lookup("open-ils.ingest.flat_marc.biblio.xml")->run($document);
430     my @mXfe = $self->method_lookup("open-ils.ingest.extract.field_entry.all.xml")->run($document);
431     my ($fp) = $self->method_lookup("open-ils.ingest.fingerprint.xml")->run($xml);
432     my ($rd) = $self->method_lookup("open-ils.ingest.descriptor.xml")->run($xml);
433
434     $_->source($bib->id) for (@mXfe);
435     $_->record($bib->id) for (@mfr);
436     $rd->record($bib->id) if ($rd);
437
438     return { full_rec => \@mfr, field_entries => \@mXfe, fingerprint => $fp, descriptor => $rd, uri => \@uris };
439 }
440 __PACKAGE__->register_method(  
441     api_name    => "open-ils.ingest.full.biblio.object.readonly",
442     method        => "ro_biblio_ingest_single_object",
443     api_level    => 1,
444     argc        => 1,
445 );                      
446
447 sub ro_biblio_ingest_single_xml {
448     my $self = shift;
449     my $client = shift;
450     my $xml = OpenILS::Application::AppUtils->entityize(shift);
451
452     my $document = $parser->parse_string($xml);
453
454     my @mfr = $self->method_lookup("open-ils.ingest.flat_marc.biblio.xml")->run($document);
455     my @mXfe = $self->method_lookup("open-ils.ingest.extract.field_entry.all.xml")->run($document);
456     my ($fp) = $self->method_lookup("open-ils.ingest.fingerprint.xml")->run($xml);
457     my ($rd) = $self->method_lookup("open-ils.ingest.descriptor.xml")->run($xml);
458
459     return { full_rec => \@mfr, field_entries => \@mXfe, fingerprint => $fp, descriptor => $rd };
460 }
461 __PACKAGE__->register_method(  
462     api_name    => "open-ils.ingest.full.biblio.xml.readonly",
463     method        => "ro_biblio_ingest_single_xml",
464     api_level    => 1,
465     argc        => 1,
466 );                      
467
468 sub ro_biblio_ingest_single_record {
469     my $self = shift;
470     my $client = shift;
471     my $rec = shift;
472
473     OpenILS::Application::Ingest->post_init();
474     my $r = OpenSRF::AppSession
475             ->create('open-ils.cstore')
476             ->request( 'open-ils.cstore.direct.biblio.record_entry.retrieve' => $rec )
477             ->gather(1);
478
479     return undef unless ($r and @$r);
480
481     my ($res) = $self->method_lookup("open-ils.ingest.full.biblio.xml.readonly")->run($r->marc);
482
483     $_->source($rec) for (@{$res->{field_entries}});
484     $_->record($rec) for (@{$res->{full_rec}});
485     $res->{descriptor}->record($rec);
486
487     return $res;
488 }
489 __PACKAGE__->register_method(  
490     api_name    => "open-ils.ingest.full.biblio.record.readonly",
491     method        => "ro_biblio_ingest_single_record",
492     api_level    => 1,
493     argc        => 1,
494 );                      
495
496 sub ro_biblio_ingest_stream_record {
497     my $self = shift;
498     my $client = shift;
499
500     OpenILS::Application::Ingest->post_init();
501
502     my $ses = OpenSRF::AppSession->create('open-ils.cstore');
503
504     while (my ($resp) = $client->recv( count => 1, timeout => 5 )) {
505     
506         my $rec = $resp->content;
507         last unless (defined $rec);
508
509         $log->debug("Running open-ils.ingest.full.biblio.record.readonly ...");
510         my ($res) = $self->method_lookup("open-ils.ingest.full.biblio.record.readonly")->run($rec);
511
512         $_->source($rec) for (@{$res->{field_entries}});
513         $_->record($rec) for (@{$res->{full_rec}});
514
515         $client->respond( $res );
516     }
517
518     return undef;
519 }
520 __PACKAGE__->register_method(  
521     api_name    => "open-ils.ingest.full.biblio.record_stream.readonly",
522     method        => "ro_biblio_ingest_stream_record",
523     api_level    => 1,
524     stream        => 1,
525 );                      
526
527 sub ro_biblio_ingest_stream_xml {
528     my $self = shift;
529     my $client = shift;
530
531     OpenILS::Application::Ingest->post_init();
532
533     my $ses = OpenSRF::AppSession->create('open-ils.cstore');
534
535     while (my ($resp) = $client->recv( count => 1, timeout => 5 )) {
536     
537         my $xml = $resp->content;
538         last unless (defined $xml);
539
540         $log->debug("Running open-ils.ingest.full.biblio.xml.readonly ...");
541         my ($res) = $self->method_lookup("open-ils.ingest.full.biblio.xml.readonly")->run($xml);
542
543         $client->respond( $res );
544     }
545
546     return undef;
547 }
548 __PACKAGE__->register_method(  
549     api_name    => "open-ils.ingest.full.biblio.xml_stream.readonly",
550     method        => "ro_biblio_ingest_stream_xml",
551     api_level    => 1,
552     stream        => 1,
553 );                      
554
555 sub rw_biblio_ingest_stream_import {
556     my $self = shift;
557     my $client = shift;
558
559     OpenILS::Application::Ingest->post_init();
560
561     my $ses = OpenSRF::AppSession->create('open-ils.cstore');
562
563     while (my ($resp) = $client->recv( count => 1, timeout => 5 )) {
564     
565         my $bib = $resp->content;
566         last unless (defined $bib);
567
568         $log->debug("Running open-ils.ingest.full.biblio.xml.readonly ...");
569         my ($res) = $self->method_lookup("open-ils.ingest.full.biblio.xml.readonly")->run($bib->marc);
570
571         $_->source($bib->id) for (@{$res->{field_entries}});
572         $_->record($bib->id) for (@{$res->{full_rec}});
573
574         $client->respond( $res );
575     }
576
577     return undef;
578 }
579 __PACKAGE__->register_method(  
580     api_name    => "open-ils.ingest.full.biblio.bib_stream.import",
581     method        => "rw_biblio_ingest_stream_import",
582     api_level    => 1,
583     stream        => 1,
584 );                      
585
586
587 # --------------------------------------------------------------------------------
588 # Authority ingest
589
590 package OpenILS::Application::Ingest::Authority;
591 use base qw/OpenILS::Application::Ingest/;
592 use Unicode::Normalize;
593
594 sub rw_authority_ingest_single_object {
595     my $self = shift;
596     my $client = shift;
597     my $auth = shift;
598
599     my ($blob) = $self->method_lookup("open-ils.ingest.full.authority.object.readonly")->run($auth);
600     return undef unless ($blob);
601
602     my $cstore = OpenSRF::AppSession->connect('open-ils.cstore');
603
604     my $xact = $cstore->request('open-ils.cstore.transaction.begin')->gather(1);
605     my $tmp;
606
607     # update full_rec stuff ...
608     $tmp = $cstore->request(
609         'open-ils.cstore.direct.authority.full_rec.id_list.atomic',
610         { record => $auth->id }
611     )->gather(1);
612
613     $cstore->request( 'open-ils.cstore.direct.authority.full_rec.delete' => $_ )->gather(1) for (@$tmp);
614     $cstore->request( 'open-ils.cstore.direct.authority.full_rec.create' => $_ )->gather(1) for (@{ $blob->{full_rec} });
615
616     # XXX when we start extracting authority descriptors and adding sources ...
617     #
618     # update rec_descriptor stuff ...
619     #$tmp = $cstore->request(
620     #    'open-ils.cstore.direct.authority.record_descriptor.id_list.atomic',
621     #    { record => $auth->id }
622     #)->gather(1);
623     #
624     #$cstore->request( 'open-ils.cstore.direct.authority.record_descriptor.delete' => $_ )->gather(1) for (@$tmp);
625     #$cstore->request( 'open-ils.cstore.direct.authority.record_descriptor.create' => $blob->{descriptor} )->gather(1);
626     #$cstore->request( 'open-ils.cstore.direct.authority.record_entry.update' => $auth )->gather(1);
627
628     $cstore->request( 'open-ils.cstore.transaction.commit' )->gather(1) || return undef;;
629     $cstore->disconnect;
630
631     return $auth->id;
632 }
633 __PACKAGE__->register_method(  
634     api_name    => "open-ils.ingest.full.authority.object",
635     method        => "rw_authority_ingest_single_object",
636     api_level    => 1,
637     argc        => 1,
638 );                      
639
640 sub rw_authority_ingest_single_record {
641     my $self = shift;
642     my $client = shift;
643     my $rec = shift;
644
645     OpenILS::Application::Ingest->post_init();
646     my $cstore = OpenSRF::AppSession->connect( 'open-ils.cstore' );
647     $cstore->request('open-ils.cstore.transaction.begin')->gather(1);
648
649     my $r = $cstore->request( 'open-ils.cstore.direct.authority.record_entry.retrieve' => $rec )->gather(1);
650
651     $cstore->request('open-ils.cstore.transaction.rollback')->gather(1);
652     $cstore->disconnect;
653
654     return undef unless ($r and @$r);
655
656     return ($self->method_lookup("open-ils.ingest.full.authority.object")->run($r))[0];
657 }
658 __PACKAGE__->register_method(  
659     api_name    => "open-ils.ingest.full.authority.record",
660     method        => "rw_authority_ingest_single_record",
661     api_level    => 1,
662     argc        => 1,
663 );                      
664
665 sub ro_authority_ingest_single_object {
666     my $self = shift;
667     my $client = shift;
668     my $bib = shift;
669     my $xml = OpenILS::Application::AppUtils->entityize($bib->marc);
670
671     my $document = $parser->parse_string($xml);
672
673     my @mfr = $self->method_lookup("open-ils.ingest.flat_marc.authority.xml")->run($document);
674
675     $_->record($bib->id) for (@mfr);
676
677     return { full_rec => \@mfr };
678 }
679 __PACKAGE__->register_method(  
680     api_name    => "open-ils.ingest.full.authority.object.readonly",
681     method        => "ro_authority_ingest_single_object",
682     api_level    => 1,
683     argc        => 1,
684 );                      
685 __PACKAGE__->register_method(  
686     api_name    => "open-ils.ingest.full.serial.object.readonly",
687     method        => "ro_authority_ingest_single_object",
688     api_level    => 1,
689     argc        => 1,
690 );                      
691
692
693 sub ro_authority_ingest_single_xml {
694     my $self = shift;
695     my $client = shift;
696     my $xml = OpenILS::Application::AppUtils->entityize(shift);
697
698     my $document = $parser->parse_string($xml);
699
700     my @mfr = $self->method_lookup("open-ils.ingest.flat_marc.authority.xml")->run($document);
701
702     return { full_rec => \@mfr };
703 }
704 __PACKAGE__->register_method(  
705     api_name    => "open-ils.ingest.full.authority.xml.readonly",
706     method        => "ro_authority_ingest_single_xml",
707     api_level    => 1,
708     argc        => 1,
709 );                      
710
711 sub ro_authority_ingest_single_record {
712     my $self = shift;
713     my $client = shift;
714     my $rec = shift;
715
716     OpenILS::Application::Ingest->post_init();
717     my $r = OpenSRF::AppSession
718             ->create('open-ils.cstore')
719             ->request( 'open-ils.cstore.direct.authority.record_entry.retrieve' => $rec )
720             ->gather(1);
721
722     return undef unless ($r and @$r);
723
724     my ($res) = $self->method_lookup("open-ils.ingest.full.authority.xml.readonly")->run($r->marc);
725
726     $_->record($rec) for (@{$res->{full_rec}});
727     $res->{descriptor}->record($rec);
728
729     return $res;
730 }
731 __PACKAGE__->register_method(  
732     api_name    => "open-ils.ingest.full.authority.record.readonly",
733     method        => "ro_authority_ingest_single_record",
734     api_level    => 1,
735     argc        => 1,
736 );                      
737
738 sub ro_authority_ingest_stream_record {
739     my $self = shift;
740     my $client = shift;
741
742     OpenILS::Application::Ingest->post_init();
743
744     my $ses = OpenSRF::AppSession->create('open-ils.cstore');
745
746     while (my ($resp) = $client->recv( count => 1, timeout => 5 )) {
747     
748         my $rec = $resp->content;
749         last unless (defined $rec);
750
751         $log->debug("Running open-ils.ingest.full.authority.record.readonly ...");
752         my ($res) = $self->method_lookup("open-ils.ingest.full.authority.record.readonly")->run($rec);
753
754         $_->record($rec) for (@{$res->{full_rec}});
755
756         $client->respond( $res );
757     }
758
759     return undef;
760 }
761 __PACKAGE__->register_method(  
762     api_name    => "open-ils.ingest.full.authority.record_stream.readonly",
763     method        => "ro_authority_ingest_stream_record",
764     api_level    => 1,
765     stream        => 1,
766 );                      
767
768 sub ro_authority_ingest_stream_xml {
769     my $self = shift;
770     my $client = shift;
771
772     OpenILS::Application::Ingest->post_init();
773
774     my $ses = OpenSRF::AppSession->create('open-ils.cstore');
775
776     while (my ($resp) = $client->recv( count => 1, timeout => 5 )) {
777     
778         my $xml = $resp->content;
779         last unless (defined $xml);
780
781         $log->debug("Running open-ils.ingest.full.authority.xml.readonly ...");
782         my ($res) = $self->method_lookup("open-ils.ingest.full.authority.xml.readonly")->run($xml);
783
784         $client->respond( $res );
785     }
786
787     return undef;
788 }
789 __PACKAGE__->register_method(  
790     api_name    => "open-ils.ingest.full.authority.xml_stream.readonly",
791     method        => "ro_authority_ingest_stream_xml",
792     api_level    => 1,
793     stream        => 1,
794 );                      
795
796 sub rw_authority_ingest_stream_import {
797     my $self = shift;
798     my $client = shift;
799
800     OpenILS::Application::Ingest->post_init();
801
802     my $ses = OpenSRF::AppSession->create('open-ils.cstore');
803
804     while (my ($resp) = $client->recv( count => 1, timeout => 5 )) {
805     
806         my $bib = $resp->content;
807         last unless (defined $bib);
808
809         $log->debug("Running open-ils.ingest.full.authority.xml.readonly ...");
810         my ($res) = $self->method_lookup("open-ils.ingest.full.authority.xml.readonly")->run($bib->marc);
811
812         $_->record($bib->id) for (@{$res->{full_rec}});
813
814         $client->respond( $res );
815     }
816
817     return undef;
818 }
819 __PACKAGE__->register_method(  
820     api_name    => "open-ils.ingest.full.authority.bib_stream.import",
821     method        => "rw_authority_ingest_stream_import",
822     api_level    => 1,
823     stream        => 1,
824 );                      
825
826 # --------------------------------------------------------------------------------
827 # Serial ingest
828
829 package OpenILS::Application::Ingest::Serial;
830 use base qw/OpenILS::Application::Ingest/;
831 use Unicode::Normalize;
832
833 sub ro_serial_ingest_single_object {
834     my $self = shift;
835     my $client = shift;
836     my $bib = shift;
837     my $xml = OpenILS::Application::AppUtils->entityize($bib->marc);
838
839     my $document = $parser->parse_string($xml);
840
841     my @mfr = $self->method_lookup("open-ils.ingest.flat_marc.serial.xml")->run($document);
842
843     $_->record($bib->id) for (@mfr);
844
845     return { full_rec => \@mfr };
846 }
847 __PACKAGE__->register_method(  
848     api_name    => "open-ils.ingest.full.serial.object.readonly",
849     method        => "ro_serial_ingest_single_object",
850     api_level    => 1,
851     argc        => 1,
852 );                      
853
854 sub ro_serial_ingest_single_xml {
855     my $self = shift;
856     my $client = shift;
857     my $xml = OpenILS::Application::AppUtils->entityize(shift);
858
859     my $document = $parser->parse_string($xml);
860
861     my @mfr = $self->method_lookup("open-ils.ingest.flat_marc.serial.xml")->run($document);
862
863     return { full_rec => \@mfr };
864 }
865 __PACKAGE__->register_method(  
866     api_name    => "open-ils.ingest.full.serial.xml.readonly",
867     method        => "ro_serial_ingest_single_xml",
868     api_level    => 1,
869     argc        => 1,
870 );                      
871
872 sub ro_serial_ingest_single_record {
873     my $self = shift;
874     my $client = shift;
875     my $rec = shift;
876
877     OpenILS::Application::Ingest->post_init();
878     my $r = OpenSRF::AppSession
879             ->create('open-ils.cstore')
880             ->request( 'open-ils.cstore.direct.serial.record_entry.retrieve' => $rec )
881             ->gather(1);
882
883     return undef unless ($r and @$r);
884
885     my ($res) = $self->method_lookup("open-ils.ingest.full.serial.xml.readonly")->run($r->marc);
886
887     $_->record($rec) for (@{$res->{full_rec}});
888     $res->{descriptor}->record($rec);
889
890     return $res;
891 }
892 __PACKAGE__->register_method(  
893     api_name    => "open-ils.ingest.full.serial.record.readonly",
894     method        => "ro_serial_ingest_single_record",
895     api_level    => 1,
896     argc        => 1,
897 );                      
898
899 sub ro_serial_ingest_stream_record {
900     my $self = shift;
901     my $client = shift;
902
903     OpenILS::Application::Ingest->post_init();
904
905     my $ses = OpenSRF::AppSession->create('open-ils.cstore');
906
907     while (my ($resp) = $client->recv( count => 1, timeout => 5 )) {
908     
909         my $rec = $resp->content;
910         last unless (defined $rec);
911
912         $log->debug("Running open-ils.ingest.full.serial.record.readonly ...");
913         my ($res) = $self->method_lookup("open-ils.ingest.full.serial.record.readonly")->run($rec);
914
915         $_->record($rec) for (@{$res->{full_rec}});
916
917         $client->respond( $res );
918     }
919
920     return undef;
921 }
922 __PACKAGE__->register_method(  
923     api_name    => "open-ils.ingest.full.serial.record_stream.readonly",
924     method        => "ro_serial_ingest_stream_record",
925     api_level    => 1,
926     stream        => 1,
927 );                      
928
929 sub ro_serial_ingest_stream_xml {
930     my $self = shift;
931     my $client = shift;
932
933     OpenILS::Application::Ingest->post_init();
934
935     my $ses = OpenSRF::AppSession->create('open-ils.cstore');
936
937     while (my ($resp) = $client->recv( count => 1, timeout => 5 )) {
938     
939         my $xml = $resp->content;
940         last unless (defined $xml);
941
942         $log->debug("Running open-ils.ingest.full.serial.xml.readonly ...");
943         my ($res) = $self->method_lookup("open-ils.ingest.full.serial.xml.readonly")->run($xml);
944
945         $client->respond( $res );
946     }
947
948     return undef;
949 }
950 __PACKAGE__->register_method(  
951     api_name    => "open-ils.ingest.full.serial.xml_stream.readonly",
952     method        => "ro_serial_ingest_stream_xml",
953     api_level    => 1,
954     stream        => 1,
955 );                      
956
957 sub rw_serial_ingest_stream_import {
958     my $self = shift;
959     my $client = shift;
960
961     OpenILS::Application::Ingest->post_init();
962
963     my $ses = OpenSRF::AppSession->create('open-ils.cstore');
964
965     while (my ($resp) = $client->recv( count => 1, timeout => 5 )) {
966     
967         my $bib = $resp->content;
968         last unless (defined $bib);
969
970         $log->debug("Running open-ils.ingest.full.serial.xml.readonly ...");
971         my ($res) = $self->method_lookup("open-ils.ingest.full.serial.xml.readonly")->run($bib->marc);
972
973         $_->record($bib->id) for (@{$res->{full_rec}});
974
975         $client->respond( $res );
976     }
977
978     return undef;
979 }
980 __PACKAGE__->register_method(  
981     api_name    => "open-ils.ingest.full.serial.bib_stream.import",
982     method        => "rw_serial_ingest_stream_import",
983     api_level    => 1,
984     stream        => 1,
985 );                      
986
987
988 # --------------------------------------------------------------------------------
989 # MARC index extraction
990
991 package OpenILS::Application::Ingest::XPATH;
992 use base qw/OpenILS::Application::Ingest/;
993 use Unicode::Normalize;
994
995 # give this an XML documentElement and an XPATH expression
996 sub xpath_to_string {
997     my $xml = shift;
998     my $xpath = shift;
999     my $ns_uri = shift;
1000     my $ns_prefix = shift;
1001     my $unique = shift;
1002
1003     $xml->setNamespace( $ns_uri, $ns_prefix, 1 ) if ($ns_uri && $ns_prefix);
1004
1005     my $string = "";
1006
1007     # grab the set of matching nodes
1008     my @nodes = $xml->findnodes( $xpath );
1009     for my $value (@nodes) {
1010
1011         # grab all children of the node
1012         my @children = $value->childNodes();
1013         for my $child (@children) {
1014
1015             # add the childs content to the growing buffer
1016             my $content = quotemeta($child->textContent);
1017             next if ($unique && $string =~ /$content/);  # uniquify the values
1018             $string .= $child->textContent . " ";
1019         }
1020         if( ! @children ) {
1021             $string .= $value->textContent . " ";
1022         }
1023     }
1024
1025     $string =~ s/(\w+)\/(\w+)/$1 $2/sgo;
1026     $string =~ s/(\d{4})-(\d{4})/$1 $2/sgo;
1027
1028     return NFD($string);
1029 }
1030
1031 sub class_index_string_xml {
1032     my $self = shift;
1033     my $client = shift;
1034     my $xml = shift;
1035     my @classes = @_;
1036
1037     OpenILS::Application::Ingest->post_init();
1038     $xml = $parser->parse_string(OpenILS::Application::AppUtils->entityize($xml)) unless (ref $xml);
1039
1040     my %transform_cache;
1041     
1042     for my $class (@classes) {
1043         my $class_constructor = "Fieldmapper::metabib::${class}_field_entry";
1044         for my $type ( keys %{ $xpathset->{$class} } ) {
1045
1046             my $def = $xpathset->{$class}->{$type};
1047             my $sf = $OpenILS::Application::Ingest::supported_formats{$def->{format}};
1048
1049             my $document = $xml;
1050
1051             if ($sf->{xslt}) {
1052                 $document = $transform_cache{$def->{format}} || $sf->{xslt}->transform($xml);
1053                 $transform_cache{$def->{format}} = $document;
1054             }
1055
1056             my $value =  xpath_to_string(
1057                     $document->documentElement    => $def->{xpath},
1058                     $sf->{ns}            => $def->{format},
1059                     1
1060             );
1061
1062             next unless $value;
1063
1064             $value = NFD($value);
1065             $value =~ s/\pM+//sgo;
1066             $value =~ s/\pC+//sgo;
1067             $value =~ s/\W+$//sgo;
1068
1069             $value =~ s/\b\.+\b//sgo;
1070             $value = lc($value);
1071
1072             my $fm = $class_constructor->new;
1073             $fm->value( $value );
1074             $fm->field( $xpathset->{$class}->{$type}->{id} );
1075             $client->respond($fm);
1076         }
1077     }
1078     return undef;
1079 }
1080 __PACKAGE__->register_method(  
1081     api_name    => "open-ils.ingest.field_entry.class.xml",
1082     method        => "class_index_string_xml",
1083     api_level    => 1,
1084     argc        => 2,
1085     stream        => 1,
1086 );                      
1087
1088 sub class_index_string_record {
1089     my $self = shift;
1090     my $client = shift;
1091     my $rec = shift;
1092     my @classes = shift;
1093
1094     OpenILS::Application::Ingest->post_init();
1095     my $r = OpenSRF::AppSession
1096             ->create('open-ils.cstore')
1097             ->request( 'open-ils.cstore.direct.authority.record_entry.retrieve' => $rec )
1098             ->gather(1);
1099
1100     return undef unless ($r and @$r);
1101
1102     for my $fm ($self->method_lookup("open-ils.ingest.field_entry.class.xml")->run($r->marc, @classes)) {
1103         $fm->source($rec);
1104         $client->respond($fm);
1105     }
1106     return undef;
1107 }
1108 __PACKAGE__->register_method(  
1109     api_name    => "open-ils.ingest.field_entry.class.record",
1110     method        => "class_index_string_record",
1111     api_level    => 1,
1112     argc        => 2,
1113     stream        => 1,
1114 );                      
1115
1116 sub all_index_string_xml {
1117     my $self = shift;
1118     my $client = shift;
1119     my $xml = shift;
1120
1121     for my $fm ($self->method_lookup("open-ils.ingest.field_entry.class.xml")->run($xml, keys(%$xpathset))) {
1122         $client->respond($fm);
1123     }
1124     return undef;
1125 }
1126 __PACKAGE__->register_method(  
1127     api_name    => "open-ils.ingest.extract.field_entry.all.xml",
1128     method        => "all_index_string_xml",
1129     api_level    => 1,
1130     argc        => 1,
1131     stream        => 1,
1132 );                      
1133
1134 sub all_index_string_record {
1135     my $self = shift;
1136     my $client = shift;
1137     my $rec = shift;
1138
1139     OpenILS::Application::Ingest->post_init();
1140     my $r = OpenSRF::AppSession
1141             ->create('open-ils.cstore')
1142             ->request( 'open-ils.cstore.direct.biblio.record_entry.retrieve' => $rec )
1143             ->gather(1);
1144
1145     return undef unless ($r and @$r);
1146
1147     for my $fm ($self->method_lookup("open-ils.ingest.field_entry.class.xml")->run($r->marc, keys(%$xpathset))) {
1148         $fm->source($rec);
1149         $client->respond($fm);
1150     }
1151     return undef;
1152 }
1153 __PACKAGE__->register_method(  
1154     api_name    => "open-ils.ingest.extract.field_entry.all.record",
1155     method        => "all_index_string_record",
1156     api_level    => 1,
1157     argc        => 1,
1158     stream        => 1,
1159 );                      
1160
1161 # --------------------------------------------------------------------------------
1162 # Flat MARC
1163
1164 package OpenILS::Application::Ingest::FlatMARC;
1165 use base qw/OpenILS::Application::Ingest/;
1166 use Unicode::Normalize;
1167
1168
1169 sub _marcxml_to_full_rows {
1170
1171     my $marcxml = shift;
1172     my $xmltype = shift || 'metabib';
1173
1174     my $type = "Fieldmapper::${xmltype}::full_rec";
1175
1176     my @ns_list;
1177     
1178     my ($root) = $marcxml->findnodes('//*[local-name()="record"]');
1179
1180     for my $tagline ( @{$root->getChildrenByTagName("leader")} ) {
1181         next unless $tagline;
1182
1183         my $ns = $type->new;
1184
1185         $ns->tag( 'LDR' );
1186         my $val = $tagline->textContent;
1187         $val = NFD($val);
1188         $val =~ s/\pM+//sgo;
1189         $val =~ s/\pC+//sgo;
1190         $val =~ s/\W+$//sgo;
1191         $ns->value( $val );
1192
1193         push @ns_list, $ns;
1194     }
1195
1196     for my $tagline ( @{$root->getChildrenByTagName("controlfield")} ) {
1197         next unless $tagline;
1198
1199         my $ns = $type->new;
1200
1201         $ns->tag( $tagline->getAttribute( "tag" ) );
1202         my $val = $tagline->textContent;
1203         $val = NFD($val);
1204         $val =~ s/\pM+//sgo;
1205         $val =~ s/\pC+//sgo;
1206         $val =~ s/\W+$//sgo;
1207         $ns->value( $val );
1208
1209         push @ns_list, $ns;
1210     }
1211
1212     for my $tagline ( @{$root->getChildrenByTagName("datafield")} ) {
1213         next unless $tagline;
1214
1215         my $tag = $tagline->getAttribute( "tag" );
1216         my $ind1 = $tagline->getAttribute( "ind1" );
1217         my $ind2 = $tagline->getAttribute( "ind2" );
1218
1219         for my $data ( @{$tagline->getChildrenByTagName('subfield')} ) {
1220             next unless $data;
1221
1222             my $ns = $type->new;
1223
1224             $ns->tag( $tag );
1225             $ns->ind1( $ind1 );
1226             $ns->ind2( $ind2 );
1227             $ns->subfield( $data->getAttribute( "code" ) );
1228             my $val = $data->textContent;
1229             $val = NFD($val);
1230             $val =~ s/\pM+//sgo;
1231             $val =~ s/\pC+//sgo;
1232             $val =~ s/\W+$//sgo;
1233             $val =~ s/(\d{4})-(\d{4})/$1 $2/sgo;
1234             $val =~ s/(\w+)\/(\w+)/$1 $2/sgo;
1235             $ns->value( lc($val) );
1236
1237             push @ns_list, $ns;
1238         }
1239
1240         if ($xmltype eq 'metabib' and $tag eq '245') {
1241                $tag = 'tnf';
1242     
1243             for my $data ( @{$tagline->getChildrenByTagName('subfield')} ) {
1244                 next unless ($data and $data->getAttribute( "code" ) eq 'a');
1245     
1246                 $ns = $type->new;
1247     
1248                 $ns->tag( $tag );
1249                 $ns->ind1( $ind1 );
1250                 $ns->ind2( $ind2 );
1251                 $ns->subfield( $data->getAttribute( "code" ) );
1252                 my $val = substr( $data->textContent, $ind2 );
1253                 $val = NFD($val);
1254                 $val =~ s/\pM+//sgo;
1255                 $val =~ s/\pC+//sgo;
1256                 $val =~ s/\W+$//sgo;
1257                 $val =~ s/(\w+)\/(\w+)/$1 $2/sgo;
1258                 $val =~ s/(\d{4})-(\d{4})/$1 $2/sgo;
1259                 $ns->value( lc($val) );
1260     
1261                 push @ns_list, $ns;
1262             }
1263         }
1264     }
1265
1266     $log->debug("Returning ".scalar(@ns_list)." Fieldmapper nodes from $xmltype xml");
1267     return @ns_list;
1268 }
1269
1270 sub flat_marc_xml {
1271     my $self = shift;
1272     my $client = shift;
1273     my $xml = shift;
1274
1275     $log->debug("processing [$xml]");
1276
1277     $xml = $parser->parse_string(OpenILS::Application::AppUtils->entityize($xml)) unless (ref $xml);
1278
1279     my $type = 'metabib';
1280     $type = 'authority' if ($self->api_name =~ /authority/o);
1281     $type = 'serial' if ($self->api_name =~ /serial/o);
1282
1283     OpenILS::Application::Ingest->post_init();
1284
1285     $client->respond($_) for (_marcxml_to_full_rows($xml, $type));
1286     return undef;
1287 }
1288 __PACKAGE__->register_method(  
1289     api_name    => "open-ils.ingest.flat_marc.authority.xml",
1290     method        => "flat_marc_xml",
1291     api_level    => 1,
1292     argc        => 1,
1293     stream        => 1,
1294 );                      
1295 __PACKAGE__->register_method(  
1296     api_name    => "open-ils.ingest.flat_marc.biblio.xml",
1297     method        => "flat_marc_xml",
1298     api_level    => 1,
1299     argc        => 1,
1300     stream        => 1,
1301 );                      
1302 __PACKAGE__->register_method(  
1303     api_name    => "open-ils.ingest.flat_marc.serial.xml",
1304     method        => "flat_marc_xml",
1305     api_level    => 1,
1306     argc        => 1,
1307     stream        => 1,
1308 );                      
1309
1310 sub flat_marc_record {
1311     my $self = shift;
1312     my $client = shift;
1313     my $rec = shift;
1314
1315     my $type = 'biblio';
1316     $type = 'authority' if ($self->api_name =~ /authority/o);
1317     $type = 'serial' if ($self->api_name =~ /serial/o);
1318
1319     OpenILS::Application::Ingest->post_init();
1320     my $r = OpenSRF::AppSession
1321             ->create('open-ils.cstore')
1322             ->request( "open-ils.cstore.direct.${type}.record_entry.retrieve" => $rec )
1323             ->gather(1);
1324
1325
1326     return undef unless ($r and $r->marc);
1327
1328     my @rows = $self->method_lookup("open-ils.ingest.flat_marc.$type.xml")->run($r->marc);
1329     for my $row (@rows) {
1330         $client->respond($row);
1331         $log->debug(OpenSRF::Utils::JSON->perl2JSON($row), DEBUG);
1332     }
1333     return undef;
1334 }
1335 __PACKAGE__->register_method(  
1336     api_name    => "open-ils.ingest.flat_marc.biblio.record_entry",
1337     method        => "flat_marc_record",
1338     api_level    => 1,
1339     argc        => 1,
1340     stream        => 1,
1341 );                      
1342 __PACKAGE__->register_method(  
1343     api_name    => "open-ils.ingest.flat_marc.authority.record_entry",
1344     method        => "flat_marc_record",
1345     api_level    => 1,
1346     argc        => 1,
1347     stream        => 1,
1348 );                      
1349 __PACKAGE__->register_method(  
1350     api_name    => "open-ils.ingest.flat_marc.serial.record_entry",
1351     method        => "flat_marc_record",
1352     api_level    => 1,
1353     argc        => 1,
1354     stream        => 1,
1355 );                      
1356
1357 # --------------------------------------------------------------------------------
1358 # URI extraction
1359
1360 package OpenILS::Application::Ingest::Biblio::URI;
1361 use base qw/OpenILS::Application::Ingest/;
1362 use Unicode::Normalize;
1363 use OpenSRF::EX qw/:try/;
1364
1365
1366 sub _extract_856_uris {
1367
1368     my $rec   = shift;
1369     my $max_cn = shift;
1370     my $max_uri = shift;
1371     my @objects;
1372     
1373     my $recid = $rec->id;
1374     my $marcxml = $rec->marc;
1375
1376     my $document = $parser->parse_string($marcxml);
1377     my @nodes = $document->findnodes('//*[local-name()="datafield" and @tag="856" and (@ind1="4" or @ind1="1") and (@ind2="0" or @ind2="1")]');
1378
1379     my $cstore = OpenSRF::AppSession->connect('open-ils.cstore');
1380
1381     my %cn_cache;
1382
1383     for my $node (@nodes) {
1384         # first, is there a URI?
1385         my $href = $node->findvalue('*[local-name()="subfield" and @code="u"]/text()');
1386         next unless ($href);
1387
1388         # now, find the best possible label
1389         my $label = $node->findvalue('*[local-name()="subfield" and @code="y"]/text()');
1390         $label ||= $node->findvalue('*[local-name()="subfield" and @code="3"]/text()');
1391         $label ||= $href;
1392
1393         # look for use info
1394         my $use = $node->findvalue('*[local-name()="subfield" and @code="z"]/text()');
1395         $use ||= $node->findvalue('*[local-name()="subfield" and @code="2"]/text()');
1396         $use ||= $node->findvalue('*[local-name()="subfield" and @code="n"]/text()');
1397
1398         # moving on to the URI owner
1399         my $owner = $node->findvalue('*[local-name()="subfield" and @code="9"]/text()'); # Evergreen special sauce
1400         $owner ||= $node->findvalue('*[local-name()="subfield" and @code="w"]/text()');
1401         $owner ||= $node->findvalue('*[local-name()="subfield" and @code="n"]/text()');
1402
1403         $owner =~ s/^.*?\((\w+)\).*$/$1/o; # unwrap first paren-enclosed string and then ...
1404
1405         # no owner? skip it :(
1406         next unless ($owner);
1407
1408         my $org = $cstore
1409             ->request( 'open-ils.cstore.direct.actor.org_unit.search' => { shortname => $owner} )
1410             ->gather(1);
1411
1412         next unless ($org);
1413
1414         # now we can construct the uri object
1415         my $uri = $cstore
1416             ->request( 'open-ils.cstore.direct.asset.uri.search' => { label => $label, href => $href, use_restriction => $use, active => 't' } )
1417             ->gather(1);
1418
1419         if (!$uri) {
1420             $uri = Fieldmapper::asset::uri->new;
1421             $uri->isnew( 1 );
1422             $uri->id( $$max_uri++ );
1423             $uri->label($label);
1424             $uri->href($href);
1425             $uri->active('t');
1426             $uri->use_restriction($use);
1427         }
1428
1429         # see if we need to create a call number
1430         my $cn = $cn_cache{$org->id};
1431         $cn = $cn->clone if ($cn);
1432         $cn->clear_isnew if ($cn);
1433
1434         $cn ||= $cstore
1435             ->request( 'open-ils.cstore.direct.asset.call_number.search' => { owning_lib => $org->id, record => $recid, label => '##URI##' } )
1436             ->gather(1);
1437
1438         if (!$cn) {
1439             $cn = Fieldmapper::asset::call_number->new;
1440             $cn->isnew( 1 );
1441             $cn->deleted('f');
1442             $cn->id( $$max_cn++ );
1443             $cn->owning_lib( $org->id );
1444             $cn->record( $recid );
1445             $cn->create_date( 'now' );
1446             $cn->creator( $rec->creator );
1447             $cn->editor( $rec->editor );
1448             $cn->edit_date( 'now' );
1449             $cn->label( '##URI##' );
1450         }
1451
1452         $cn_cache{$org->id} = $cn;
1453
1454         push @objects, { uri => $uri, call_number => $cn };
1455     }
1456
1457     $log->debug("Returning ".scalar(@objects)." URI nodes for record $recid");
1458     $cstore->disconnect;
1459     return @objects;
1460 }
1461
1462 sub get_uris_record {
1463     my $self = shift;
1464     my $client = shift;
1465     my $rec = shift;
1466
1467     OpenILS::Application::Ingest->post_init();
1468     my $r = OpenSRF::AppSession
1469             ->create('open-ils.cstore')
1470             ->request( "open-ils.cstore.direct.biblio.record_entry.retrieve" => $rec )
1471             ->gather(1);
1472
1473     return undef unless ($r and $r->marc);
1474
1475     $client->respond($_) for (_extract_856_uris($r));
1476     return undef;
1477 }
1478 __PACKAGE__->register_method(  
1479     api_name    => "open-ils.ingest.856_uri.record",
1480     method        => "get_uris_record",
1481     api_level    => 1,
1482     argc        => 1,
1483     stream        => 1,
1484 );                      
1485
1486 sub get_uris_object {
1487     my $self = shift;
1488     my $client = shift;
1489     my $obj = shift;
1490     my $max_cn = shift;
1491     my $max_uri = shift;
1492
1493     return undef unless ($obj and $obj->marc);
1494
1495     $client->respond($_) for (_extract_856_uris($obj, \$max_cn, \$max_uri));
1496     return undef;
1497 }
1498 __PACKAGE__->register_method(  
1499     api_name    => "open-ils.ingest.856_uri.object",
1500     method        => "get_uris_object",
1501     api_level    => 1,
1502     argc        => 1,
1503     stream        => 1,
1504 );                      
1505
1506
1507 # --------------------------------------------------------------------------------
1508 # Fingerprinting
1509
1510 package OpenILS::Application::Ingest::Biblio::Fingerprint;
1511 use base qw/OpenILS::Application::Ingest/;
1512 use Unicode::Normalize;
1513 use OpenSRF::EX qw/:try/;
1514
1515 sub biblio_fingerprint_record {
1516     my $self = shift;
1517     my $client = shift;
1518     my $rec = shift;
1519
1520     OpenILS::Application::Ingest->post_init();
1521
1522     my $r = OpenSRF::AppSession
1523             ->create('open-ils.cstore')
1524             ->request( 'open-ils.cstore.direct.biblio.record_entry.retrieve' => $rec )
1525             ->gather(1);
1526
1527     return undef unless ($r and $r->marc);
1528
1529     my ($fp) = $self->method_lookup('open-ils.ingest.fingerprint.xml')->run($r->marc);
1530     $log->debug("Returning [$fp] as fingerprint for record $rec", INFO);
1531     $fp->{quality} = int($fp->{quality});
1532     return $fp;
1533 }
1534 __PACKAGE__->register_method(  
1535     api_name    => "open-ils.ingest.fingerprint.record",
1536     method        => "biblio_fingerprint_record",
1537     api_level    => 1,
1538     argc        => 1,
1539 );                      
1540
1541 our $fp_script;
1542 sub biblio_fingerprint {
1543     my $self = shift;
1544     my $client = shift;
1545     my $xml = OpenILS::Application::AppUtils->entityize(shift);
1546
1547     $log->internal("Got MARC [$xml]");
1548
1549     if(!$fp_script) {
1550         my @pfx = ( "apps", "open-ils.ingest","app_settings" );
1551         my $conf = OpenSRF::Utils::SettingsClient->new;
1552
1553         my $libs        = $conf->config_value(@pfx, 'script_path');
1554         my $script_file = $conf->config_value(@pfx, 'scripts', 'biblio_fingerprint');
1555         my $script_libs = (ref($libs)) ? $libs : [$libs];
1556
1557         $log->debug("Loading script $script_file for biblio fingerprinting...");
1558         
1559         $fp_script = new OpenILS::Utils::ScriptRunner
1560             ( file        => $script_file,
1561               paths        => $script_libs,
1562               reset_count    => 100 );
1563     }
1564
1565     $fp_script->insert('environment' => {marc => $xml} => 1);
1566
1567     my $res = $fp_script->run || ($log->error( "Fingerprint script died!  $@" ) && return undef);
1568     $log->debug("Script for biblio fingerprinting completed successfully...");
1569
1570     return $res;
1571 }
1572 __PACKAGE__->register_method(  
1573     api_name    => "open-ils.ingest.fingerprint.xml",
1574     method        => "biblio_fingerprint",
1575     api_level    => 1,
1576     argc        => 1,
1577 );                      
1578
1579 our $rd_script;
1580 sub biblio_descriptor {
1581     my $self = shift;
1582     my $client = shift;
1583     my $xml = OpenILS::Application::AppUtils->entityize(shift);
1584
1585     $log->internal("Got MARC [$xml]");
1586
1587     if(!$rd_script) {
1588         my @pfx = ( "apps", "open-ils.ingest","app_settings" );
1589         my $conf = OpenSRF::Utils::SettingsClient->new;
1590
1591         my $libs        = $conf->config_value(@pfx, 'script_path');
1592         my $script_file = $conf->config_value(@pfx, 'scripts', 'biblio_descriptor');
1593         my $script_libs = (ref($libs)) ? $libs : [$libs];
1594
1595         $log->debug("Loading script $script_file for biblio descriptor extraction...");
1596         
1597         $rd_script = new OpenILS::Utils::ScriptRunner
1598             ( file        => $script_file,
1599               paths        => $script_libs,
1600               reset_count    => 100 );
1601     }
1602
1603     $log->debug("Setting up environment for descriptor extraction script...");
1604     $rd_script->insert('environment.marc' => $xml => 1);
1605     $log->debug("Environment building complete...");
1606
1607     my $res = $rd_script->run || ($log->error( "Descriptor script died!  $@" ) && return undef);
1608     $log->debug("Script for biblio descriptor extraction completed successfully");
1609
1610     my $d1 = $res->date1;
1611     if ($d1 && $d1 ne '    ') {
1612         $d1 =~ tr/ux/00/;
1613         $res->date1( $d1 );
1614     }
1615
1616     my $d2 = $res->date2;
1617     if ($d2 && $d2 ne '    ') {
1618         $d2 =~ tr/ux/99/;
1619         $res->date2( $d2 );
1620     }
1621
1622     return $res;
1623 }
1624 __PACKAGE__->register_method(  
1625     api_name    => "open-ils.ingest.descriptor.xml",
1626     method        => "biblio_descriptor",
1627     api_level    => 1,
1628     argc        => 1,
1629 );                      
1630
1631
1632 1;
1633
1634 # vim:et:ts=4:sw=4: