]> git.evergreen-ils.org Git - working/Evergreen.git/blob - Open-ILS/src/perlmods/OpenILS/Application/Ingest.pm
Remove duplicate mapping for open-ils.ingest.full.serial.object.readonly
[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
688 sub ro_authority_ingest_single_xml {
689     my $self = shift;
690     my $client = shift;
691     my $xml = OpenILS::Application::AppUtils->entityize(shift);
692
693     my $document = $parser->parse_string($xml);
694
695     my @mfr = $self->method_lookup("open-ils.ingest.flat_marc.authority.xml")->run($document);
696
697     return { full_rec => \@mfr };
698 }
699 __PACKAGE__->register_method(  
700     api_name    => "open-ils.ingest.full.authority.xml.readonly",
701     method        => "ro_authority_ingest_single_xml",
702     api_level    => 1,
703     argc        => 1,
704 );                      
705
706 sub ro_authority_ingest_single_record {
707     my $self = shift;
708     my $client = shift;
709     my $rec = shift;
710
711     OpenILS::Application::Ingest->post_init();
712     my $r = OpenSRF::AppSession
713             ->create('open-ils.cstore')
714             ->request( 'open-ils.cstore.direct.authority.record_entry.retrieve' => $rec )
715             ->gather(1);
716
717     return undef unless ($r and @$r);
718
719     my ($res) = $self->method_lookup("open-ils.ingest.full.authority.xml.readonly")->run($r->marc);
720
721     $_->record($rec) for (@{$res->{full_rec}});
722     $res->{descriptor}->record($rec);
723
724     return $res;
725 }
726 __PACKAGE__->register_method(  
727     api_name    => "open-ils.ingest.full.authority.record.readonly",
728     method        => "ro_authority_ingest_single_record",
729     api_level    => 1,
730     argc        => 1,
731 );                      
732
733 sub ro_authority_ingest_stream_record {
734     my $self = shift;
735     my $client = shift;
736
737     OpenILS::Application::Ingest->post_init();
738
739     my $ses = OpenSRF::AppSession->create('open-ils.cstore');
740
741     while (my ($resp) = $client->recv( count => 1, timeout => 5 )) {
742     
743         my $rec = $resp->content;
744         last unless (defined $rec);
745
746         $log->debug("Running open-ils.ingest.full.authority.record.readonly ...");
747         my ($res) = $self->method_lookup("open-ils.ingest.full.authority.record.readonly")->run($rec);
748
749         $_->record($rec) for (@{$res->{full_rec}});
750
751         $client->respond( $res );
752     }
753
754     return undef;
755 }
756 __PACKAGE__->register_method(  
757     api_name    => "open-ils.ingest.full.authority.record_stream.readonly",
758     method        => "ro_authority_ingest_stream_record",
759     api_level    => 1,
760     stream        => 1,
761 );                      
762
763 sub ro_authority_ingest_stream_xml {
764     my $self = shift;
765     my $client = shift;
766
767     OpenILS::Application::Ingest->post_init();
768
769     my $ses = OpenSRF::AppSession->create('open-ils.cstore');
770
771     while (my ($resp) = $client->recv( count => 1, timeout => 5 )) {
772     
773         my $xml = $resp->content;
774         last unless (defined $xml);
775
776         $log->debug("Running open-ils.ingest.full.authority.xml.readonly ...");
777         my ($res) = $self->method_lookup("open-ils.ingest.full.authority.xml.readonly")->run($xml);
778
779         $client->respond( $res );
780     }
781
782     return undef;
783 }
784 __PACKAGE__->register_method(  
785     api_name    => "open-ils.ingest.full.authority.xml_stream.readonly",
786     method        => "ro_authority_ingest_stream_xml",
787     api_level    => 1,
788     stream        => 1,
789 );                      
790
791 sub rw_authority_ingest_stream_import {
792     my $self = shift;
793     my $client = shift;
794
795     OpenILS::Application::Ingest->post_init();
796
797     my $ses = OpenSRF::AppSession->create('open-ils.cstore');
798
799     while (my ($resp) = $client->recv( count => 1, timeout => 5 )) {
800     
801         my $bib = $resp->content;
802         last unless (defined $bib);
803
804         $log->debug("Running open-ils.ingest.full.authority.xml.readonly ...");
805         my ($res) = $self->method_lookup("open-ils.ingest.full.authority.xml.readonly")->run($bib->marc);
806
807         $_->record($bib->id) for (@{$res->{full_rec}});
808
809         $client->respond( $res );
810     }
811
812     return undef;
813 }
814 __PACKAGE__->register_method(  
815     api_name    => "open-ils.ingest.full.authority.bib_stream.import",
816     method        => "rw_authority_ingest_stream_import",
817     api_level    => 1,
818     stream        => 1,
819 );                      
820
821 # --------------------------------------------------------------------------------
822 # Serial ingest
823
824 package OpenILS::Application::Ingest::Serial;
825 use base qw/OpenILS::Application::Ingest/;
826 use Unicode::Normalize;
827
828 sub ro_serial_ingest_single_object {
829     my $self = shift;
830     my $client = shift;
831     my $bib = shift;
832     my $xml = OpenILS::Application::AppUtils->entityize($bib->marc);
833
834     my $document = $parser->parse_string($xml);
835
836     my @mfr = $self->method_lookup("open-ils.ingest.flat_marc.serial.xml")->run($document);
837
838     $_->record($bib->id) for (@mfr);
839
840     return { full_rec => \@mfr };
841 }
842 __PACKAGE__->register_method(  
843     api_name    => "open-ils.ingest.full.serial.object.readonly",
844     method        => "ro_serial_ingest_single_object",
845     api_level    => 1,
846     argc        => 1,
847 );                      
848
849 sub ro_serial_ingest_single_xml {
850     my $self = shift;
851     my $client = shift;
852     my $xml = OpenILS::Application::AppUtils->entityize(shift);
853
854     my $document = $parser->parse_string($xml);
855
856     my @mfr = $self->method_lookup("open-ils.ingest.flat_marc.serial.xml")->run($document);
857
858     return { full_rec => \@mfr };
859 }
860 __PACKAGE__->register_method(  
861     api_name    => "open-ils.ingest.full.serial.xml.readonly",
862     method        => "ro_serial_ingest_single_xml",
863     api_level    => 1,
864     argc        => 1,
865 );                      
866
867 sub ro_serial_ingest_single_record {
868     my $self = shift;
869     my $client = shift;
870     my $rec = shift;
871
872     OpenILS::Application::Ingest->post_init();
873     my $r = OpenSRF::AppSession
874             ->create('open-ils.cstore')
875             ->request( 'open-ils.cstore.direct.serial.record_entry.retrieve' => $rec )
876             ->gather(1);
877
878     return undef unless ($r and @$r);
879
880     my ($res) = $self->method_lookup("open-ils.ingest.full.serial.xml.readonly")->run($r->marc);
881
882     $_->record($rec) for (@{$res->{full_rec}});
883     $res->{descriptor}->record($rec);
884
885     return $res;
886 }
887 __PACKAGE__->register_method(  
888     api_name    => "open-ils.ingest.full.serial.record.readonly",
889     method        => "ro_serial_ingest_single_record",
890     api_level    => 1,
891     argc        => 1,
892 );                      
893
894 sub ro_serial_ingest_stream_record {
895     my $self = shift;
896     my $client = shift;
897
898     OpenILS::Application::Ingest->post_init();
899
900     my $ses = OpenSRF::AppSession->create('open-ils.cstore');
901
902     while (my ($resp) = $client->recv( count => 1, timeout => 5 )) {
903     
904         my $rec = $resp->content;
905         last unless (defined $rec);
906
907         $log->debug("Running open-ils.ingest.full.serial.record.readonly ...");
908         my ($res) = $self->method_lookup("open-ils.ingest.full.serial.record.readonly")->run($rec);
909
910         $_->record($rec) for (@{$res->{full_rec}});
911
912         $client->respond( $res );
913     }
914
915     return undef;
916 }
917 __PACKAGE__->register_method(  
918     api_name    => "open-ils.ingest.full.serial.record_stream.readonly",
919     method        => "ro_serial_ingest_stream_record",
920     api_level    => 1,
921     stream        => 1,
922 );                      
923
924 sub ro_serial_ingest_stream_xml {
925     my $self = shift;
926     my $client = shift;
927
928     OpenILS::Application::Ingest->post_init();
929
930     my $ses = OpenSRF::AppSession->create('open-ils.cstore');
931
932     while (my ($resp) = $client->recv( count => 1, timeout => 5 )) {
933     
934         my $xml = $resp->content;
935         last unless (defined $xml);
936
937         $log->debug("Running open-ils.ingest.full.serial.xml.readonly ...");
938         my ($res) = $self->method_lookup("open-ils.ingest.full.serial.xml.readonly")->run($xml);
939
940         $client->respond( $res );
941     }
942
943     return undef;
944 }
945 __PACKAGE__->register_method(  
946     api_name    => "open-ils.ingest.full.serial.xml_stream.readonly",
947     method        => "ro_serial_ingest_stream_xml",
948     api_level    => 1,
949     stream        => 1,
950 );                      
951
952 sub rw_serial_ingest_stream_import {
953     my $self = shift;
954     my $client = shift;
955
956     OpenILS::Application::Ingest->post_init();
957
958     my $ses = OpenSRF::AppSession->create('open-ils.cstore');
959
960     while (my ($resp) = $client->recv( count => 1, timeout => 5 )) {
961     
962         my $bib = $resp->content;
963         last unless (defined $bib);
964
965         $log->debug("Running open-ils.ingest.full.serial.xml.readonly ...");
966         my ($res) = $self->method_lookup("open-ils.ingest.full.serial.xml.readonly")->run($bib->marc);
967
968         $_->record($bib->id) for (@{$res->{full_rec}});
969
970         $client->respond( $res );
971     }
972
973     return undef;
974 }
975 __PACKAGE__->register_method(  
976     api_name    => "open-ils.ingest.full.serial.bib_stream.import",
977     method        => "rw_serial_ingest_stream_import",
978     api_level    => 1,
979     stream        => 1,
980 );                      
981
982
983 # --------------------------------------------------------------------------------
984 # MARC index extraction
985
986 package OpenILS::Application::Ingest::XPATH;
987 use base qw/OpenILS::Application::Ingest/;
988 use Unicode::Normalize;
989
990 # give this an XML documentElement and an XPATH expression
991 sub xpath_to_string {
992     my $xml = shift;
993     my $xpath = shift;
994     my $ns_uri = shift;
995     my $ns_prefix = shift;
996     my $unique = shift;
997
998     $xml->setNamespace( $ns_uri, $ns_prefix, 1 ) if ($ns_uri && $ns_prefix);
999
1000     my $string = "";
1001
1002     # grab the set of matching nodes
1003     my @nodes = $xml->findnodes( $xpath );
1004     for my $value (@nodes) {
1005
1006         # grab all children of the node
1007         my @children = $value->childNodes();
1008         for my $child (@children) {
1009
1010             # add the childs content to the growing buffer
1011             my $content = quotemeta($child->textContent);
1012             next if ($unique && $string =~ /$content/);  # uniquify the values
1013             $string .= $child->textContent . " ";
1014         }
1015         if( ! @children ) {
1016             $string .= $value->textContent . " ";
1017         }
1018     }
1019
1020     $string =~ s/(\w+)\/(\w+)/$1 $2/sgo;
1021     # Split date ranges and ISSNs on the hyphen
1022     $string =~ s/(\d{4})-(\d{3,4}x?)/ $1 $2 /goi;
1023
1024     return NFD($string);
1025 }
1026
1027 sub class_index_string_xml {
1028     my $self = shift;
1029     my $client = shift;
1030     my $xml = shift;
1031     my @classes = @_;
1032
1033     OpenILS::Application::Ingest->post_init();
1034     $xml = $parser->parse_string(OpenILS::Application::AppUtils->entityize($xml)) unless (ref $xml);
1035
1036     my %transform_cache;
1037     
1038     for my $class (@classes) {
1039         my $class_constructor = "Fieldmapper::metabib::${class}_field_entry";
1040         for my $type ( keys %{ $xpathset->{$class} } ) {
1041
1042             my $def = $xpathset->{$class}->{$type};
1043             my $sf = $OpenILS::Application::Ingest::supported_formats{$def->{format}};
1044
1045             my $document = $xml;
1046
1047             if ($sf->{xslt}) {
1048                 $document = $transform_cache{$def->{format}} || $sf->{xslt}->transform($xml);
1049                 $transform_cache{$def->{format}} = $document;
1050             }
1051
1052             my $value =  xpath_to_string(
1053                     $document->documentElement    => $def->{xpath},
1054                     $sf->{ns}            => $def->{format},
1055                     1
1056             );
1057
1058             next unless $value;
1059
1060             $value = NFD($value);
1061             $value =~ s/\pM+//sgo;
1062             $value =~ s/\pC+//sgo;
1063             $value =~ s/\W+$//sgo;
1064
1065             $value =~ s/\b\.+\b//sgo;
1066             $value = lc($value);
1067
1068             my $fm = $class_constructor->new;
1069             $fm->value( $value );
1070             $fm->field( $xpathset->{$class}->{$type}->{id} );
1071             $client->respond($fm);
1072         }
1073     }
1074     return undef;
1075 }
1076 __PACKAGE__->register_method(  
1077     api_name    => "open-ils.ingest.field_entry.class.xml",
1078     method        => "class_index_string_xml",
1079     api_level    => 1,
1080     argc        => 2,
1081     stream        => 1,
1082 );                      
1083
1084 sub class_index_string_record {
1085     my $self = shift;
1086     my $client = shift;
1087     my $rec = shift;
1088     my @classes = shift;
1089
1090     OpenILS::Application::Ingest->post_init();
1091     my $r = OpenSRF::AppSession
1092             ->create('open-ils.cstore')
1093             ->request( 'open-ils.cstore.direct.authority.record_entry.retrieve' => $rec )
1094             ->gather(1);
1095
1096     return undef unless ($r and @$r);
1097
1098     for my $fm ($self->method_lookup("open-ils.ingest.field_entry.class.xml")->run($r->marc, @classes)) {
1099         $fm->source($rec);
1100         $client->respond($fm);
1101     }
1102     return undef;
1103 }
1104 __PACKAGE__->register_method(  
1105     api_name    => "open-ils.ingest.field_entry.class.record",
1106     method        => "class_index_string_record",
1107     api_level    => 1,
1108     argc        => 2,
1109     stream        => 1,
1110 );                      
1111
1112 sub all_index_string_xml {
1113     my $self = shift;
1114     my $client = shift;
1115     my $xml = shift;
1116
1117     for my $fm ($self->method_lookup("open-ils.ingest.field_entry.class.xml")->run($xml, keys(%$xpathset))) {
1118         $client->respond($fm);
1119     }
1120     return undef;
1121 }
1122 __PACKAGE__->register_method(  
1123     api_name    => "open-ils.ingest.extract.field_entry.all.xml",
1124     method        => "all_index_string_xml",
1125     api_level    => 1,
1126     argc        => 1,
1127     stream        => 1,
1128 );                      
1129
1130 sub all_index_string_record {
1131     my $self = shift;
1132     my $client = shift;
1133     my $rec = shift;
1134
1135     OpenILS::Application::Ingest->post_init();
1136     my $r = OpenSRF::AppSession
1137             ->create('open-ils.cstore')
1138             ->request( 'open-ils.cstore.direct.biblio.record_entry.retrieve' => $rec )
1139             ->gather(1);
1140
1141     return undef unless ($r and @$r);
1142
1143     for my $fm ($self->method_lookup("open-ils.ingest.field_entry.class.xml")->run($r->marc, keys(%$xpathset))) {
1144         $fm->source($rec);
1145         $client->respond($fm);
1146     }
1147     return undef;
1148 }
1149 __PACKAGE__->register_method(  
1150     api_name    => "open-ils.ingest.extract.field_entry.all.record",
1151     method        => "all_index_string_record",
1152     api_level    => 1,
1153     argc        => 1,
1154     stream        => 1,
1155 );                      
1156
1157 # --------------------------------------------------------------------------------
1158 # Flat MARC
1159
1160 package OpenILS::Application::Ingest::FlatMARC;
1161 use base qw/OpenILS::Application::Ingest/;
1162 use Unicode::Normalize;
1163
1164
1165 sub _marcxml_to_full_rows {
1166
1167     my $marcxml = shift;
1168     my $xmltype = shift || 'metabib';
1169
1170     my $type = "Fieldmapper::${xmltype}::full_rec";
1171
1172     my @ns_list;
1173     
1174     my ($root) = $marcxml->findnodes('//*[local-name()="record"]');
1175
1176     for my $tagline ( @{$root->getChildrenByTagName("leader")} ) {
1177         next unless $tagline;
1178         _special_tag_to_full_rows($type, $tagline, \@ns_list, 'LDR');
1179     }
1180
1181     for my $tagline ( @{$root->getChildrenByTagName("controlfield")} ) {
1182         next unless $tagline;
1183         _special_tag_to_full_rows($type, $tagline, \@ns_list, $tagline->getAttribute( "tag" ));
1184     }
1185
1186     for my $tagline ( @{$root->getChildrenByTagName("datafield")} ) {
1187         next unless $tagline;
1188         _data_tag_to_full_rows($type, $tagline, \@ns_list, $tagline->getAttribute( "tag" ));
1189
1190         if ($xmltype eq 'metabib' and $tag eq '245') {
1191             _data_tag_to_full_rows($type, $tagline, \@ns_list, 'tnf');
1192         }
1193     }
1194
1195     $log->debug("Returning ".scalar(@ns_list)." Fieldmapper nodes from $xmltype xml");
1196     return @ns_list;
1197 }
1198
1199 =head2 _special_tag_to_full_rows
1200
1201 Converts a leader or control field to a set of normalized values
1202
1203 =cut
1204
1205 sub _special_tag_to_full_rows {
1206     my $type = shift;
1207     my $tagline = shift;
1208     my $ns_list = shift;
1209     my $tagname = shift;
1210
1211     my $ns = $type->new;
1212
1213     $ns->tag( $tagname );
1214     my $val = $tagline->textContent;
1215     $val = NFD($val);
1216     $val =~ s/\pM+//sgo;
1217     $val =~ s/\pC+//sgo;
1218     $val =~ s/\W+$//sgo;
1219     $ns->value( $val );
1220
1221     push @$ns_list, $ns;
1222 }
1223
1224 =head2 _data_tag_to_full_rows
1225
1226 Converts a data field to a set of normalized values
1227
1228 =cut
1229
1230 sub _data_tag_to_full_rows {
1231     my $type = shift;
1232     my $tagline = shift;
1233     my $ns_list = shift;
1234     my $tag = shift;
1235
1236     my $ind1 = $tagline->getAttribute( "ind1" );
1237     my $ind2 = $tagline->getAttribute( "ind2" );
1238
1239     for my $data ( @{$tagline->getChildrenByTagName('subfield')} ) {
1240         next unless $data;
1241
1242         my $ns = $type->new;
1243
1244         $ns->tag( $tag );
1245         $ns->ind1( $ind1 );
1246         $ns->ind2( $ind2 );
1247         $ns->subfield( $data->getAttribute( "code" ) );
1248         my $val = $data->textContent;
1249         $val = NFD($val);
1250         $val =~ s/\pM+//sgo;
1251         $val =~ s/\pC+//sgo;
1252         $val =~ s/\W+$//sgo;
1253         # Split date ranges and ISSNs on the hyphen
1254         $val =~ s/(\d{4})-(\d{3,4}x?)/ $1 $2 /goi;
1255         $val =~ s/(\w+)\/(\w+)/$1 $2/sgo;
1256         $ns->value( lc($val) );
1257
1258         push @$ns_list, $ns;
1259     }
1260 }
1261
1262 sub flat_marc_xml {
1263     my $self = shift;
1264     my $client = shift;
1265     my $xml = shift;
1266
1267     $log->debug("processing [$xml]");
1268
1269     $xml = $parser->parse_string(OpenILS::Application::AppUtils->entityize($xml)) unless (ref $xml);
1270
1271     my $type = 'metabib';
1272     $type = 'authority' if ($self->api_name =~ /authority/o);
1273     $type = 'serial' if ($self->api_name =~ /serial/o);
1274
1275     OpenILS::Application::Ingest->post_init();
1276
1277     $client->respond($_) for (_marcxml_to_full_rows($xml, $type));
1278     return undef;
1279 }
1280 __PACKAGE__->register_method(  
1281     api_name    => "open-ils.ingest.flat_marc.authority.xml",
1282     method        => "flat_marc_xml",
1283     api_level    => 1,
1284     argc        => 1,
1285     stream        => 1,
1286 );                      
1287 __PACKAGE__->register_method(  
1288     api_name    => "open-ils.ingest.flat_marc.biblio.xml",
1289     method        => "flat_marc_xml",
1290     api_level    => 1,
1291     argc        => 1,
1292     stream        => 1,
1293 );                      
1294 __PACKAGE__->register_method(  
1295     api_name    => "open-ils.ingest.flat_marc.serial.xml",
1296     method        => "flat_marc_xml",
1297     api_level    => 1,
1298     argc        => 1,
1299     stream        => 1,
1300 );                      
1301
1302 sub flat_marc_record {
1303     my $self = shift;
1304     my $client = shift;
1305     my $rec = shift;
1306
1307     my $type = 'biblio';
1308     $type = 'authority' if ($self->api_name =~ /authority/o);
1309     $type = 'serial' if ($self->api_name =~ /serial/o);
1310
1311     OpenILS::Application::Ingest->post_init();
1312     my $r = OpenSRF::AppSession
1313             ->create('open-ils.cstore')
1314             ->request( "open-ils.cstore.direct.${type}.record_entry.retrieve" => $rec )
1315             ->gather(1);
1316
1317
1318     return undef unless ($r and $r->marc);
1319
1320     my @rows = $self->method_lookup("open-ils.ingest.flat_marc.$type.xml")->run($r->marc);
1321     for my $row (@rows) {
1322         $client->respond($row);
1323         $log->debug(OpenSRF::Utils::JSON->perl2JSON($row), DEBUG);
1324     }
1325     return undef;
1326 }
1327 __PACKAGE__->register_method(  
1328     api_name    => "open-ils.ingest.flat_marc.biblio.record_entry",
1329     method        => "flat_marc_record",
1330     api_level    => 1,
1331     argc        => 1,
1332     stream        => 1,
1333 );                      
1334 __PACKAGE__->register_method(  
1335     api_name    => "open-ils.ingest.flat_marc.authority.record_entry",
1336     method        => "flat_marc_record",
1337     api_level    => 1,
1338     argc        => 1,
1339     stream        => 1,
1340 );                      
1341 __PACKAGE__->register_method(  
1342     api_name    => "open-ils.ingest.flat_marc.serial.record_entry",
1343     method        => "flat_marc_record",
1344     api_level    => 1,
1345     argc        => 1,
1346     stream        => 1,
1347 );                      
1348
1349 # --------------------------------------------------------------------------------
1350 # URI extraction
1351
1352 package OpenILS::Application::Ingest::Biblio::URI;
1353 use base qw/OpenILS::Application::Ingest/;
1354 use Unicode::Normalize;
1355 use OpenSRF::EX qw/:try/;
1356
1357
1358 sub _extract_856_uris {
1359
1360     my $rec   = shift;
1361     my $max_cn = shift;
1362     my $max_uri = shift;
1363     my @objects;
1364     
1365     my $recid = $rec->id;
1366     my $marcxml = $rec->marc;
1367
1368     my $document = $parser->parse_string($marcxml);
1369     my @nodes = $document->findnodes('//*[local-name()="datafield" and @tag="856" and (@ind1="4" or @ind1="1") and (@ind2="0" or @ind2="1")]');
1370
1371     my $cstore = OpenSRF::AppSession->connect('open-ils.cstore');
1372
1373     my %cn_cache;
1374
1375     for my $node (@nodes) {
1376         # first, is there a URI?
1377         my $href = $node->findvalue('*[local-name()="subfield" and @code="u"]/text()');
1378         next unless ($href);
1379
1380         # now, find the best possible label
1381         my $label = $node->findvalue('*[local-name()="subfield" and @code="y"]/text()');
1382         $label ||= $node->findvalue('*[local-name()="subfield" and @code="3"]/text()');
1383         $label ||= $href;
1384
1385         # look for use info
1386         my $use = $node->findvalue('*[local-name()="subfield" and @code="z"]/text()');
1387         $use ||= $node->findvalue('*[local-name()="subfield" and @code="2"]/text()');
1388         $use ||= $node->findvalue('*[local-name()="subfield" and @code="n"]/text()');
1389
1390         # moving on to the URI owner
1391         my $owner = $node->findvalue('*[local-name()="subfield" and @code="9"]/text()'); # Evergreen special sauce
1392         $owner ||= $node->findvalue('*[local-name()="subfield" and @code="w"]/text()');
1393         $owner ||= $node->findvalue('*[local-name()="subfield" and @code="n"]/text()');
1394
1395         $owner =~ s/^.*?\((\w+)\).*$/$1/o; # unwrap first paren-enclosed string and then ...
1396
1397         # no owner? skip it :(
1398         next unless ($owner);
1399
1400         my $org = $cstore
1401             ->request( 'open-ils.cstore.direct.actor.org_unit.search' => { shortname => $owner} )
1402             ->gather(1);
1403
1404         next unless ($org);
1405
1406         # now we can construct the uri object
1407         my $uri = $cstore
1408             ->request( 'open-ils.cstore.direct.asset.uri.search' => { label => $label, href => $href, use_restriction => $use, active => 't' } )
1409             ->gather(1);
1410
1411         if (!$uri) {
1412             $uri = Fieldmapper::asset::uri->new;
1413             $uri->isnew( 1 );
1414             $uri->id( $$max_uri++ );
1415             $uri->label($label);
1416             $uri->href($href);
1417             $uri->active('t');
1418             $uri->use_restriction($use);
1419         }
1420
1421         # see if we need to create a call number
1422         my $cn = $cn_cache{$org->id};
1423         $cn = $cn->clone if ($cn);
1424         $cn->clear_isnew if ($cn);
1425
1426         $cn ||= $cstore
1427             ->request( 'open-ils.cstore.direct.asset.call_number.search' => { owning_lib => $org->id, record => $recid, label => '##URI##' } )
1428             ->gather(1);
1429
1430         if (!$cn) {
1431             $cn = Fieldmapper::asset::call_number->new;
1432             $cn->isnew( 1 );
1433             $cn->deleted('f');
1434             $cn->id( $$max_cn++ );
1435             $cn->owning_lib( $org->id );
1436             $cn->record( $recid );
1437             $cn->create_date( 'now' );
1438             $cn->creator( $rec->creator );
1439             $cn->editor( $rec->editor );
1440             $cn->edit_date( 'now' );
1441             $cn->label( '##URI##' );
1442         }
1443
1444         $cn_cache{$org->id} = $cn;
1445
1446         push @objects, { uri => $uri, call_number => $cn };
1447     }
1448
1449     $log->debug("Returning ".scalar(@objects)." URI nodes for record $recid");
1450     $cstore->disconnect;
1451     return @objects;
1452 }
1453
1454 sub get_uris_record {
1455     my $self = shift;
1456     my $client = shift;
1457     my $rec = shift;
1458
1459     OpenILS::Application::Ingest->post_init();
1460     my $r = OpenSRF::AppSession
1461             ->create('open-ils.cstore')
1462             ->request( "open-ils.cstore.direct.biblio.record_entry.retrieve" => $rec )
1463             ->gather(1);
1464
1465     return undef unless ($r and $r->marc);
1466
1467     $client->respond($_) for (_extract_856_uris($r));
1468     return undef;
1469 }
1470 __PACKAGE__->register_method(  
1471     api_name    => "open-ils.ingest.856_uri.record",
1472     method        => "get_uris_record",
1473     api_level    => 1,
1474     argc        => 1,
1475     stream        => 1,
1476 );                      
1477
1478 sub get_uris_object {
1479     my $self = shift;
1480     my $client = shift;
1481     my $obj = shift;
1482     my $max_cn = shift;
1483     my $max_uri = shift;
1484
1485     return undef unless ($obj and $obj->marc);
1486
1487     $client->respond($_) for (_extract_856_uris($obj, \$max_cn, \$max_uri));
1488     return undef;
1489 }
1490 __PACKAGE__->register_method(  
1491     api_name    => "open-ils.ingest.856_uri.object",
1492     method        => "get_uris_object",
1493     api_level    => 1,
1494     argc        => 1,
1495     stream        => 1,
1496 );                      
1497
1498
1499 # --------------------------------------------------------------------------------
1500 # Fingerprinting
1501
1502 package OpenILS::Application::Ingest::Biblio::Fingerprint;
1503 use base qw/OpenILS::Application::Ingest/;
1504 use Unicode::Normalize;
1505 use OpenSRF::EX qw/:try/;
1506
1507 sub biblio_fingerprint_record {
1508     my $self = shift;
1509     my $client = shift;
1510     my $rec = shift;
1511
1512     OpenILS::Application::Ingest->post_init();
1513
1514     my $r = OpenSRF::AppSession
1515             ->create('open-ils.cstore')
1516             ->request( 'open-ils.cstore.direct.biblio.record_entry.retrieve' => $rec )
1517             ->gather(1);
1518
1519     return undef unless ($r and $r->marc);
1520
1521     my ($fp) = $self->method_lookup('open-ils.ingest.fingerprint.xml')->run($r->marc);
1522     $log->debug("Returning [$fp] as fingerprint for record $rec", INFO);
1523     $fp->{quality} = int($fp->{quality});
1524     return $fp;
1525 }
1526 __PACKAGE__->register_method(  
1527     api_name    => "open-ils.ingest.fingerprint.record",
1528     method        => "biblio_fingerprint_record",
1529     api_level    => 1,
1530     argc        => 1,
1531 );                      
1532
1533 our $fp_script;
1534 sub biblio_fingerprint {
1535     my $self = shift;
1536     my $client = shift;
1537     my $xml = OpenILS::Application::AppUtils->entityize(shift);
1538
1539     $log->internal("Got MARC [$xml]");
1540
1541     if(!$fp_script) {
1542         my @pfx = ( "apps", "open-ils.ingest","app_settings" );
1543         my $conf = OpenSRF::Utils::SettingsClient->new;
1544
1545         my $libs        = $conf->config_value(@pfx, 'script_path');
1546         my $script_file = $conf->config_value(@pfx, 'scripts', 'biblio_fingerprint');
1547         my $script_libs = (ref($libs)) ? $libs : [$libs];
1548
1549         $log->debug("Loading script $script_file for biblio fingerprinting...");
1550         
1551         $fp_script = new OpenILS::Utils::ScriptRunner
1552             ( file        => $script_file,
1553               paths        => $script_libs,
1554               reset_count    => 100 );
1555     }
1556
1557     $fp_script->insert('environment' => {marc => $xml} => 1);
1558
1559     my $res = $fp_script->run || ($log->error( "Fingerprint script died!  $@" ) && return undef);
1560     $log->debug("Script for biblio fingerprinting completed successfully...");
1561
1562     return $res;
1563 }
1564 __PACKAGE__->register_method(  
1565     api_name    => "open-ils.ingest.fingerprint.xml",
1566     method        => "biblio_fingerprint",
1567     api_level    => 1,
1568     argc        => 1,
1569 );                      
1570
1571 our $rd_script;
1572 sub biblio_descriptor {
1573     my $self = shift;
1574     my $client = shift;
1575     my $xml = OpenILS::Application::AppUtils->entityize(shift);
1576
1577     $log->internal("Got MARC [$xml]");
1578
1579     if(!$rd_script) {
1580         my @pfx = ( "apps", "open-ils.ingest","app_settings" );
1581         my $conf = OpenSRF::Utils::SettingsClient->new;
1582
1583         my $libs        = $conf->config_value(@pfx, 'script_path');
1584         my $script_file = $conf->config_value(@pfx, 'scripts', 'biblio_descriptor');
1585         my $script_libs = (ref($libs)) ? $libs : [$libs];
1586
1587         $log->debug("Loading script $script_file for biblio descriptor extraction...");
1588         
1589         $rd_script = new OpenILS::Utils::ScriptRunner
1590             ( file        => $script_file,
1591               paths        => $script_libs,
1592               reset_count    => 100 );
1593     }
1594
1595     $log->debug("Setting up environment for descriptor extraction script...");
1596     $rd_script->insert('environment.marc' => $xml => 1);
1597     $log->debug("Environment building complete...");
1598
1599     my $res = $rd_script->run || ($log->error( "Descriptor script died!  $@" ) && return undef);
1600     $log->debug("Script for biblio descriptor extraction completed successfully");
1601
1602     my $d1 = $res->date1;
1603     if ($d1 && $d1 ne '    ') {
1604         $d1 =~ tr/ux/00/;
1605         $res->date1( $d1 );
1606     }
1607
1608     my $d2 = $res->date2;
1609     if ($d2 && $d2 ne '    ') {
1610         $d2 =~ tr/ux/99/;
1611         $res->date2( $d2 );
1612     }
1613
1614     return $res;
1615 }
1616 __PACKAGE__->register_method(  
1617     api_name    => "open-ils.ingest.descriptor.xml",
1618     method        => "biblio_descriptor",
1619     api_level    => 1,
1620     argc        => 1,
1621 );                      
1622
1623
1624 1;
1625
1626 # vim:et:ts=4:sw=4: