]> git.evergreen-ils.org Git - working/Evergreen.git/blob - Open-ILS/src/perlmods/OpenILS/Application/Ingest.pm
adding authority ingest methods -- by id (record) or object
[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         my $umap = Fieldmapper::asset::uri_call_number_map->new;
179         $umap->uri($u->{uri}->id);
180         $umap->call_number($u->{call_number}->id);
181
182         $cstore->request( 'open-ils.cstore.direct.asset.uri_call_number_map.create' => $umap )->gather(1) if (!$tmp);
183     }
184
185     # update full_rec stuff ...
186     $tmp = $cstore->request(
187         'open-ils.cstore.direct.metabib.full_rec.id_list.atomic',
188         { record => $bib->id }
189     )->gather(1);
190
191     $cstore->request( 'open-ils.cstore.direct.metabib.full_rec.delete' => $_ )->gather(1) for (@$tmp);
192     $cstore->request( 'open-ils.cstore.direct.metabib.full_rec.create' => $_ )->gather(1) for (@{ $blob->{full_rec} });
193
194     # update rec_descriptor stuff ...
195     $tmp = $cstore->request(
196         'open-ils.cstore.direct.metabib.record_descriptor.id_list.atomic',
197         { record => $bib->id }
198     )->gather(1);
199
200     $cstore->request( 'open-ils.cstore.direct.metabib.record_descriptor.delete' => $_ )->gather(1) for (@$tmp);
201     $cstore->request( 'open-ils.cstore.direct.metabib.record_descriptor.create' => $blob->{descriptor} )->gather(1);
202
203     # deal with classed fields...
204     for my $class ( qw/title author subject keyword series/ ) {
205         $tmp = $cstore->request(
206             "open-ils.cstore.direct.metabib.${class}_field_entry.id_list.atomic",
207             { source => $bib->id }
208         )->gather(1);
209
210         $cstore->request( "open-ils.cstore.direct.metabib.${class}_field_entry.delete" => $_ )->gather(1) for (@$tmp);
211     }
212     for my $obj ( @{ $blob->{field_entries} } ) {
213         my $class = $obj->class_name;
214         $class =~ s/^Fieldmapper:://o;
215         $class =~ s/::/./go;
216         $cstore->request( "open-ils.cstore.direct.$class.create" => $obj )->gather(1);
217     }
218
219     # update MR map ...
220
221     $tmp = $cstore->request(
222         'open-ils.cstore.direct.metabib.metarecord_source_map.search.atomic',
223         { source => $bib->id }
224     )->gather(1);
225
226     $cstore->request( 'open-ils.cstore.direct.metabib.metarecord_source_map.delete' => $_->id )->gather(1) for (@$tmp);
227
228     # get the old MRs
229     my $old_mrs = $cstore->request(
230         'open-ils.cstore.direct.metabib.metarecord.search.atomic' => { id => [map { $_->metarecord } @$tmp] }
231     )->gather(1) if (@$tmp);
232
233     $old_mrs = [] if (!ref($old_mrs));
234
235     my $mr;
236     for my $m (@$old_mrs) {
237         if ($m->fingerprint eq $bib->fingerprint) {
238             $mr = $m;
239         } else {
240             my $others = $cstore->request(
241                 'open-ils.cstore.direct.metabib.metarecord_source_map.id_list.atomic' => { metarecord => $m->id }
242             )->gather(1);
243
244             if (!@$others) {
245                 $cstore->request(
246                     'open-ils.cstore.direct.metabib.metarecord.delete' => $m->id
247                 )->gather(1);
248             }
249
250             $m->isdeleted(1);
251         }
252     }
253
254     my $holds;
255     if (!$mr) {
256         # Get the matchin MR, if any.
257         $mr = $cstore->request(
258             'open-ils.cstore.direct.metabib.metarecord.search',
259             { fingerprint => $bib->fingerprint }
260         )->gather(1);
261
262         $holds = $cstore->request(
263             'open-ils.cstore.direct.action.hold_request.search.atomic',
264             { hold_type => 'M', target => [ map { $_->id } grep { $_->isdeleted } @$old_mrs ] }
265         )->gather(1) if (@$old_mrs);
266
267         if ($mr) {
268             for my $h (@$holds) {
269                 $h->target($mr);
270                 $cstore->request( 'open-ils.cstore.direct.action.hold_request.update' => $h )->gather(1);
271                 $h->ischanged(1);
272             }
273         }
274     }
275
276     if (!$mr) {
277         $mr = new Fieldmapper::metabib::metarecord;
278         $mr->fingerprint( $bib->fingerprint );
279         $mr->master_record( $bib->id );
280         $mr->id(
281             $cstore->request(
282                 "open-ils.cstore.direct.metabib.metarecord.create",
283                 $mr => { quiet => 'true' }
284             )->gather(1)
285         );
286
287         for my $h (grep { !$_->ischanged } @$holds) {
288             $h->target($mr);
289             $cstore->request( 'open-ils.cstore.direct.action.hold_request.update' => $h )->gather(1);
290         }
291     } else {
292         my $mrm = $cstore->request(
293             'open-ils.cstore.direct.metabib.metarecord_source_map.search.atomic',
294             { metarecord => $mr->id }
295         )->gather(1);
296
297         if (@$mrm) {
298             my $best = $cstore->request(
299                 "open-ils.cstore.direct.biblio.record_entry.search",
300                 { id => [ map { $_->source } @$mrm ] },
301                 { 'select'    => { bre => [ qw/id quality/ ] },
302                   order_by    => { bre => "quality desc" },
303                   limit        => 1,
304                 }
305             )->gather(1);
306
307             if ($best->quality > $bib->quality) {
308                 $mr->master_record($best->id);
309             } else {
310                 $mr->master_record($bib->id);
311             }
312         } else {
313             $mr->master_record($bib->id);
314         }
315
316         $mr->clear_mods;
317
318         $cstore->request( 'open-ils.cstore.direct.metabib.metarecord.update' => $mr )->gather(1);
319     }
320
321     my $mrm = new Fieldmapper::metabib::metarecord_source_map;
322     $mrm->source($bib->id);
323     $mrm->metarecord($mr->id);
324
325     $cstore->request( 'open-ils.cstore.direct.metabib.metarecord_source_map.create' => $mrm )->gather(1);
326     $cstore->request( 'open-ils.cstore.direct.biblio.record_entry.update' => $bib )->gather(1);
327
328     $cstore->request( 'open-ils.cstore.transaction.commit' )->gather(1) || return undef;;
329     $cstore->disconnect;
330
331     return $bib->id;
332 }
333 __PACKAGE__->register_method(  
334     api_name    => "open-ils.ingest.full.biblio.object",
335     method        => "rw_biblio_ingest_single_object",
336     api_level    => 1,
337     argc        => 1,
338 );                      
339
340 sub rw_biblio_ingest_single_record {
341     my $self = shift;
342     my $client = shift;
343     my $rec = shift;
344
345     OpenILS::Application::Ingest->post_init();
346     my $cstore = OpenSRF::AppSession->connect( 'open-ils.cstore' );
347     $cstore->request('open-ils.cstore.transaction.begin')->gather(1);
348
349     my $r = $cstore->request( 'open-ils.cstore.direct.biblio.record_entry.retrieve' => $rec )->gather(1);
350
351     $cstore->request('open-ils.cstore.transaction.rollback')->gather(1);
352     $cstore->disconnect;
353
354     return undef unless ($r and @$r);
355
356     return ($self->method_lookup("open-ils.ingest.full.biblio.object")->run($r))[0];
357 }
358 __PACKAGE__->register_method(  
359     api_name    => "open-ils.ingest.full.biblio.record",
360     method        => "rw_biblio_ingest_single_record",
361     api_level    => 1,
362     argc        => 1,
363 );                      
364
365 sub rw_biblio_ingest_record_list {
366     my $self = shift;
367     my $client = shift;
368     my @rec = ref($_[0]) ? @{ $_[0] } : @_ ;
369
370     OpenILS::Application::Ingest->post_init();
371     my $cstore = OpenSRF::AppSession->connect( 'open-ils.cstore' );
372     $cstore->request('open-ils.cstore.transaction.begin')->gather(1);
373
374     my $r = $cstore->request( 'open-ils.cstore.direct.biblio.record_entry.search.atomic' => { id => \@rec } )->gather(1);
375
376     $cstore->request('open-ils.cstore.transaction.rollback')->gather(1);
377     $cstore->disconnect;
378
379     return undef unless ($r and @$r);
380
381     my $count = 0;
382     for (@$r) {
383         if (($self->method_lookup("open-ils.ingest.full.biblio.object")->run($_))[0]) {
384             $count++
385         }
386     }
387     return $count;
388 }
389 __PACKAGE__->register_method(  
390     api_name    => "open-ils.ingest.full.biblio.record_list",
391     method        => "rw_biblio_ingest_record_list",
392     api_level    => 1,
393     argc        => 1,
394 );                      
395
396 sub ro_biblio_ingest_single_object {
397     my $self = shift;
398     my $client = shift;
399     my $bib = shift;
400     my $xml = OpenILS::Application::AppUtils->entityize($bib->marc);
401     my $max_cn = shift;
402     my $max_uri = shift;
403
404     my $cstore = OpenSRF::AppSession->connect( 'open-ils.cstore' );
405
406     if (!$max_cn) {
407         my $cn = $cstore->request( 'open-ils.cstore.direct.asset.call_number.search' => { id => { '!=' => undef } }, { limit => 1, order_by => { acn => 'id desc' } } )->gather(1);
408         $max_cn = int($cn->id) + 1000;
409     }
410
411     if (!$max_uri) {
412         my $cn = $cstore->request( 'open-ils.cstore.direct.asset.call_number.search' => { id => { '!=' => undef } }, { limit => 1, order_by => { acn => 'id desc' } } )->gather(1);
413         $max_uri = int($cn->id) + 1000;
414     }
415
416     $cstore->disconnect;
417
418     my $document = $parser->parse_string($xml);
419
420     my @uris = $self->method_lookup("open-ils.ingest.856_uri.object")->run($bib, $max_cn, $max_uri);
421     my @mfr = $self->method_lookup("open-ils.ingest.flat_marc.biblio.xml")->run($document);
422     my @mXfe = $self->method_lookup("open-ils.ingest.extract.field_entry.all.xml")->run($document);
423     my ($fp) = $self->method_lookup("open-ils.ingest.fingerprint.xml")->run($xml);
424     my ($rd) = $self->method_lookup("open-ils.ingest.descriptor.xml")->run($xml);
425
426     $_->source($bib->id) for (@mXfe);
427     $_->record($bib->id) for (@mfr);
428     $rd->record($bib->id) if ($rd);
429
430     return { full_rec => \@mfr, field_entries => \@mXfe, fingerprint => $fp, descriptor => $rd, uri => \@uris };
431 }
432 __PACKAGE__->register_method(  
433     api_name    => "open-ils.ingest.full.biblio.object.readonly",
434     method        => "ro_biblio_ingest_single_object",
435     api_level    => 1,
436     argc        => 1,
437 );                      
438
439 sub ro_biblio_ingest_single_xml {
440     my $self = shift;
441     my $client = shift;
442     my $xml = OpenILS::Application::AppUtils->entityize(shift);
443
444     my $document = $parser->parse_string($xml);
445
446     my @mfr = $self->method_lookup("open-ils.ingest.flat_marc.biblio.xml")->run($document);
447     my @mXfe = $self->method_lookup("open-ils.ingest.extract.field_entry.all.xml")->run($document);
448     my ($fp) = $self->method_lookup("open-ils.ingest.fingerprint.xml")->run($xml);
449     my ($rd) = $self->method_lookup("open-ils.ingest.descriptor.xml")->run($xml);
450
451     return { full_rec => \@mfr, field_entries => \@mXfe, fingerprint => $fp, descriptor => $rd };
452 }
453 __PACKAGE__->register_method(  
454     api_name    => "open-ils.ingest.full.biblio.xml.readonly",
455     method        => "ro_biblio_ingest_single_xml",
456     api_level    => 1,
457     argc        => 1,
458 );                      
459
460 sub ro_biblio_ingest_single_record {
461     my $self = shift;
462     my $client = shift;
463     my $rec = shift;
464
465     OpenILS::Application::Ingest->post_init();
466     my $r = OpenSRF::AppSession
467             ->create('open-ils.cstore')
468             ->request( 'open-ils.cstore.direct.biblio.record_entry.retrieve' => $rec )
469             ->gather(1);
470
471     return undef unless ($r and @$r);
472
473     my ($res) = $self->method_lookup("open-ils.ingest.full.biblio.xml.readonly")->run($r->marc);
474
475     $_->source($rec) for (@{$res->{field_entries}});
476     $_->record($rec) for (@{$res->{full_rec}});
477     $res->{descriptor}->record($rec);
478
479     return $res;
480 }
481 __PACKAGE__->register_method(  
482     api_name    => "open-ils.ingest.full.biblio.record.readonly",
483     method        => "ro_biblio_ingest_single_record",
484     api_level    => 1,
485     argc        => 1,
486 );                      
487
488 sub ro_biblio_ingest_stream_record {
489     my $self = shift;
490     my $client = shift;
491
492     OpenILS::Application::Ingest->post_init();
493
494     my $ses = OpenSRF::AppSession->create('open-ils.cstore');
495
496     while (my ($resp) = $client->recv( count => 1, timeout => 5 )) {
497     
498         my $rec = $resp->content;
499         last unless (defined $rec);
500
501         $log->debug("Running open-ils.ingest.full.biblio.record.readonly ...");
502         my ($res) = $self->method_lookup("open-ils.ingest.full.biblio.record.readonly")->run($rec);
503
504         $_->source($rec) for (@{$res->{field_entries}});
505         $_->record($rec) for (@{$res->{full_rec}});
506
507         $client->respond( $res );
508     }
509
510     return undef;
511 }
512 __PACKAGE__->register_method(  
513     api_name    => "open-ils.ingest.full.biblio.record_stream.readonly",
514     method        => "ro_biblio_ingest_stream_record",
515     api_level    => 1,
516     stream        => 1,
517 );                      
518
519 sub ro_biblio_ingest_stream_xml {
520     my $self = shift;
521     my $client = shift;
522
523     OpenILS::Application::Ingest->post_init();
524
525     my $ses = OpenSRF::AppSession->create('open-ils.cstore');
526
527     while (my ($resp) = $client->recv( count => 1, timeout => 5 )) {
528     
529         my $xml = $resp->content;
530         last unless (defined $xml);
531
532         $log->debug("Running open-ils.ingest.full.biblio.xml.readonly ...");
533         my ($res) = $self->method_lookup("open-ils.ingest.full.biblio.xml.readonly")->run($xml);
534
535         $client->respond( $res );
536     }
537
538     return undef;
539 }
540 __PACKAGE__->register_method(  
541     api_name    => "open-ils.ingest.full.biblio.xml_stream.readonly",
542     method        => "ro_biblio_ingest_stream_xml",
543     api_level    => 1,
544     stream        => 1,
545 );                      
546
547 sub rw_biblio_ingest_stream_import {
548     my $self = shift;
549     my $client = shift;
550
551     OpenILS::Application::Ingest->post_init();
552
553     my $ses = OpenSRF::AppSession->create('open-ils.cstore');
554
555     while (my ($resp) = $client->recv( count => 1, timeout => 5 )) {
556     
557         my $bib = $resp->content;
558         last unless (defined $bib);
559
560         $log->debug("Running open-ils.ingest.full.biblio.xml.readonly ...");
561         my ($res) = $self->method_lookup("open-ils.ingest.full.biblio.xml.readonly")->run($bib->marc);
562
563         $_->source($bib->id) for (@{$res->{field_entries}});
564         $_->record($bib->id) for (@{$res->{full_rec}});
565
566         $client->respond( $res );
567     }
568
569     return undef;
570 }
571 __PACKAGE__->register_method(  
572     api_name    => "open-ils.ingest.full.biblio.bib_stream.import",
573     method        => "rw_biblio_ingest_stream_import",
574     api_level    => 1,
575     stream        => 1,
576 );                      
577
578
579 # --------------------------------------------------------------------------------
580 # Authority ingest
581
582 package OpenILS::Application::Ingest::Authority;
583 use base qw/OpenILS::Application::Ingest/;
584 use Unicode::Normalize;
585
586 sub rw_authority_ingest_single_object {
587     my $self = shift;
588     my $client = shift;
589     my $auth = shift;
590
591     my ($blob) = $self->method_lookup("open-ils.ingest.full.authority.object.readonly")->run($auth);
592     return undef unless ($blob);
593
594     my $cstore = OpenSRF::AppSession->connect('open-ils.cstore');
595
596     my $xact = $cstore->request('open-ils.cstore.transaction.begin')->gather(1);
597     my $tmp;
598
599     # update full_rec stuff ...
600     $tmp = $cstore->request(
601         'open-ils.cstore.direct.authority.full_rec.id_list.atomic',
602         { record => $auth->id }
603     )->gather(1);
604
605     $cstore->request( 'open-ils.cstore.direct.authority.full_rec.delete' => $_ )->gather(1) for (@$tmp);
606     $cstore->request( 'open-ils.cstore.direct.authority.full_rec.create' => $_ )->gather(1) for (@{ $blob->{full_rec} });
607
608     # XXX when we start extracting authority descriptors and adding sources ...
609     #
610     # update rec_descriptor stuff ...
611     #$tmp = $cstore->request(
612     #    'open-ils.cstore.direct.authority.record_descriptor.id_list.atomic',
613     #    { record => $auth->id }
614     #)->gather(1);
615     #
616     #$cstore->request( 'open-ils.cstore.direct.authority.record_descriptor.delete' => $_ )->gather(1) for (@$tmp);
617     #$cstore->request( 'open-ils.cstore.direct.authority.record_descriptor.create' => $blob->{descriptor} )->gather(1);
618     #$cstore->request( 'open-ils.cstore.direct.authority.record_entry.update' => $auth )->gather(1);
619
620     $cstore->request( 'open-ils.cstore.transaction.commit' )->gather(1) || return undef;;
621     $cstore->disconnect;
622
623     return $auth->id;
624 }
625 __PACKAGE__->register_method(  
626     api_name    => "open-ils.ingest.full.authority.object",
627     method        => "rw_authority_ingest_single_object",
628     api_level    => 1,
629     argc        => 1,
630 );                      
631
632 sub rw_authority_ingest_single_record {
633     my $self = shift;
634     my $client = shift;
635     my $rec = shift;
636
637     OpenILS::Application::Ingest->post_init();
638     my $cstore = OpenSRF::AppSession->connect( 'open-ils.cstore' );
639     $cstore->request('open-ils.cstore.transaction.begin')->gather(1);
640
641     my $r = $cstore->request( 'open-ils.cstore.direct.authority.record_entry.retrieve' => $rec )->gather(1);
642
643     $cstore->request('open-ils.cstore.transaction.rollback')->gather(1);
644     $cstore->disconnect;
645
646     return undef unless ($r and @$r);
647
648     return ($self->method_lookup("open-ils.ingest.full.authority.object")->run($r))[0];
649 }
650 __PACKAGE__->register_method(  
651     api_name    => "open-ils.ingest.full.authority.record",
652     method        => "rw_authority_ingest_single_record",
653     api_level    => 1,
654     argc        => 1,
655 );                      
656
657 sub ro_authority_ingest_single_object {
658     my $self = shift;
659     my $client = shift;
660     my $bib = shift;
661     my $xml = OpenILS::Application::AppUtils->entityize($bib->marc);
662
663     my $document = $parser->parse_string($xml);
664
665     my @mfr = $self->method_lookup("open-ils.ingest.flat_marc.authority.xml")->run($document);
666
667     $_->record($bib->id) for (@mfr);
668
669     return { full_rec => \@mfr };
670 }
671 __PACKAGE__->register_method(  
672     api_name    => "open-ils.ingest.full.authority.object.readonly",
673     method        => "ro_authority_ingest_single_object",
674     api_level    => 1,
675     argc        => 1,
676 );                      
677 __PACKAGE__->register_method(  
678     api_name    => "open-ils.ingest.full.serial.object.readonly",
679     method        => "ro_authority_ingest_single_object",
680     api_level    => 1,
681     argc        => 1,
682 );                      
683
684
685 sub ro_authority_ingest_single_xml {
686     my $self = shift;
687     my $client = shift;
688     my $xml = OpenILS::Application::AppUtils->entityize(shift);
689
690     my $document = $parser->parse_string($xml);
691
692     my @mfr = $self->method_lookup("open-ils.ingest.flat_marc.authority.xml")->run($document);
693
694     return { full_rec => \@mfr };
695 }
696 __PACKAGE__->register_method(  
697     api_name    => "open-ils.ingest.full.authority.xml.readonly",
698     method        => "ro_authority_ingest_single_xml",
699     api_level    => 1,
700     argc        => 1,
701 );                      
702
703 sub ro_authority_ingest_single_record {
704     my $self = shift;
705     my $client = shift;
706     my $rec = shift;
707
708     OpenILS::Application::Ingest->post_init();
709     my $r = OpenSRF::AppSession
710             ->create('open-ils.cstore')
711             ->request( 'open-ils.cstore.direct.authority.record_entry.retrieve' => $rec )
712             ->gather(1);
713
714     return undef unless ($r and @$r);
715
716     my ($res) = $self->method_lookup("open-ils.ingest.full.authority.xml.readonly")->run($r->marc);
717
718     $_->record($rec) for (@{$res->{full_rec}});
719     $res->{descriptor}->record($rec);
720
721     return $res;
722 }
723 __PACKAGE__->register_method(  
724     api_name    => "open-ils.ingest.full.authority.record.readonly",
725     method        => "ro_authority_ingest_single_record",
726     api_level    => 1,
727     argc        => 1,
728 );                      
729
730 sub ro_authority_ingest_stream_record {
731     my $self = shift;
732     my $client = shift;
733
734     OpenILS::Application::Ingest->post_init();
735
736     my $ses = OpenSRF::AppSession->create('open-ils.cstore');
737
738     while (my ($resp) = $client->recv( count => 1, timeout => 5 )) {
739     
740         my $rec = $resp->content;
741         last unless (defined $rec);
742
743         $log->debug("Running open-ils.ingest.full.authority.record.readonly ...");
744         my ($res) = $self->method_lookup("open-ils.ingest.full.authority.record.readonly")->run($rec);
745
746         $_->record($rec) for (@{$res->{full_rec}});
747
748         $client->respond( $res );
749     }
750
751     return undef;
752 }
753 __PACKAGE__->register_method(  
754     api_name    => "open-ils.ingest.full.authority.record_stream.readonly",
755     method        => "ro_authority_ingest_stream_record",
756     api_level    => 1,
757     stream        => 1,
758 );                      
759
760 sub ro_authority_ingest_stream_xml {
761     my $self = shift;
762     my $client = shift;
763
764     OpenILS::Application::Ingest->post_init();
765
766     my $ses = OpenSRF::AppSession->create('open-ils.cstore');
767
768     while (my ($resp) = $client->recv( count => 1, timeout => 5 )) {
769     
770         my $xml = $resp->content;
771         last unless (defined $xml);
772
773         $log->debug("Running open-ils.ingest.full.authority.xml.readonly ...");
774         my ($res) = $self->method_lookup("open-ils.ingest.full.authority.xml.readonly")->run($xml);
775
776         $client->respond( $res );
777     }
778
779     return undef;
780 }
781 __PACKAGE__->register_method(  
782     api_name    => "open-ils.ingest.full.authority.xml_stream.readonly",
783     method        => "ro_authority_ingest_stream_xml",
784     api_level    => 1,
785     stream        => 1,
786 );                      
787
788 sub rw_authority_ingest_stream_import {
789     my $self = shift;
790     my $client = shift;
791
792     OpenILS::Application::Ingest->post_init();
793
794     my $ses = OpenSRF::AppSession->create('open-ils.cstore');
795
796     while (my ($resp) = $client->recv( count => 1, timeout => 5 )) {
797     
798         my $bib = $resp->content;
799         last unless (defined $bib);
800
801         $log->debug("Running open-ils.ingest.full.authority.xml.readonly ...");
802         my ($res) = $self->method_lookup("open-ils.ingest.full.authority.xml.readonly")->run($bib->marc);
803
804         $_->record($bib->id) for (@{$res->{full_rec}});
805
806         $client->respond( $res );
807     }
808
809     return undef;
810 }
811 __PACKAGE__->register_method(  
812     api_name    => "open-ils.ingest.full.authority.bib_stream.import",
813     method        => "rw_authority_ingest_stream_import",
814     api_level    => 1,
815     stream        => 1,
816 );                      
817
818 # --------------------------------------------------------------------------------
819 # Serial ingest
820
821 package OpenILS::Application::Ingest::Serial;
822 use base qw/OpenILS::Application::Ingest/;
823 use Unicode::Normalize;
824
825 sub ro_serial_ingest_single_object {
826     my $self = shift;
827     my $client = shift;
828     my $bib = shift;
829     my $xml = OpenILS::Application::AppUtils->entityize($bib->marc);
830
831     my $document = $parser->parse_string($xml);
832
833     my @mfr = $self->method_lookup("open-ils.ingest.flat_marc.serial.xml")->run($document);
834
835     $_->record($bib->id) for (@mfr);
836
837     return { full_rec => \@mfr };
838 }
839 __PACKAGE__->register_method(  
840     api_name    => "open-ils.ingest.full.serial.object.readonly",
841     method        => "ro_serial_ingest_single_object",
842     api_level    => 1,
843     argc        => 1,
844 );                      
845
846 sub ro_serial_ingest_single_xml {
847     my $self = shift;
848     my $client = shift;
849     my $xml = OpenILS::Application::AppUtils->entityize(shift);
850
851     my $document = $parser->parse_string($xml);
852
853     my @mfr = $self->method_lookup("open-ils.ingest.flat_marc.serial.xml")->run($document);
854
855     return { full_rec => \@mfr };
856 }
857 __PACKAGE__->register_method(  
858     api_name    => "open-ils.ingest.full.serial.xml.readonly",
859     method        => "ro_serial_ingest_single_xml",
860     api_level    => 1,
861     argc        => 1,
862 );                      
863
864 sub ro_serial_ingest_single_record {
865     my $self = shift;
866     my $client = shift;
867     my $rec = shift;
868
869     OpenILS::Application::Ingest->post_init();
870     my $r = OpenSRF::AppSession
871             ->create('open-ils.cstore')
872             ->request( 'open-ils.cstore.direct.serial.record_entry.retrieve' => $rec )
873             ->gather(1);
874
875     return undef unless ($r and @$r);
876
877     my ($res) = $self->method_lookup("open-ils.ingest.full.serial.xml.readonly")->run($r->marc);
878
879     $_->record($rec) for (@{$res->{full_rec}});
880     $res->{descriptor}->record($rec);
881
882     return $res;
883 }
884 __PACKAGE__->register_method(  
885     api_name    => "open-ils.ingest.full.serial.record.readonly",
886     method        => "ro_serial_ingest_single_record",
887     api_level    => 1,
888     argc        => 1,
889 );                      
890
891 sub ro_serial_ingest_stream_record {
892     my $self = shift;
893     my $client = shift;
894
895     OpenILS::Application::Ingest->post_init();
896
897     my $ses = OpenSRF::AppSession->create('open-ils.cstore');
898
899     while (my ($resp) = $client->recv( count => 1, timeout => 5 )) {
900     
901         my $rec = $resp->content;
902         last unless (defined $rec);
903
904         $log->debug("Running open-ils.ingest.full.serial.record.readonly ...");
905         my ($res) = $self->method_lookup("open-ils.ingest.full.serial.record.readonly")->run($rec);
906
907         $_->record($rec) for (@{$res->{full_rec}});
908
909         $client->respond( $res );
910     }
911
912     return undef;
913 }
914 __PACKAGE__->register_method(  
915     api_name    => "open-ils.ingest.full.serial.record_stream.readonly",
916     method        => "ro_serial_ingest_stream_record",
917     api_level    => 1,
918     stream        => 1,
919 );                      
920
921 sub ro_serial_ingest_stream_xml {
922     my $self = shift;
923     my $client = shift;
924
925     OpenILS::Application::Ingest->post_init();
926
927     my $ses = OpenSRF::AppSession->create('open-ils.cstore');
928
929     while (my ($resp) = $client->recv( count => 1, timeout => 5 )) {
930     
931         my $xml = $resp->content;
932         last unless (defined $xml);
933
934         $log->debug("Running open-ils.ingest.full.serial.xml.readonly ...");
935         my ($res) = $self->method_lookup("open-ils.ingest.full.serial.xml.readonly")->run($xml);
936
937         $client->respond( $res );
938     }
939
940     return undef;
941 }
942 __PACKAGE__->register_method(  
943     api_name    => "open-ils.ingest.full.serial.xml_stream.readonly",
944     method        => "ro_serial_ingest_stream_xml",
945     api_level    => 1,
946     stream        => 1,
947 );                      
948
949 sub rw_serial_ingest_stream_import {
950     my $self = shift;
951     my $client = shift;
952
953     OpenILS::Application::Ingest->post_init();
954
955     my $ses = OpenSRF::AppSession->create('open-ils.cstore');
956
957     while (my ($resp) = $client->recv( count => 1, timeout => 5 )) {
958     
959         my $bib = $resp->content;
960         last unless (defined $bib);
961
962         $log->debug("Running open-ils.ingest.full.serial.xml.readonly ...");
963         my ($res) = $self->method_lookup("open-ils.ingest.full.serial.xml.readonly")->run($bib->marc);
964
965         $_->record($bib->id) for (@{$res->{full_rec}});
966
967         $client->respond( $res );
968     }
969
970     return undef;
971 }
972 __PACKAGE__->register_method(  
973     api_name    => "open-ils.ingest.full.serial.bib_stream.import",
974     method        => "rw_serial_ingest_stream_import",
975     api_level    => 1,
976     stream        => 1,
977 );                      
978
979
980 # --------------------------------------------------------------------------------
981 # MARC index extraction
982
983 package OpenILS::Application::Ingest::XPATH;
984 use base qw/OpenILS::Application::Ingest/;
985 use Unicode::Normalize;
986
987 # give this an XML documentElement and an XPATH expression
988 sub xpath_to_string {
989     my $xml = shift;
990     my $xpath = shift;
991     my $ns_uri = shift;
992     my $ns_prefix = shift;
993     my $unique = shift;
994
995     $xml->setNamespace( $ns_uri, $ns_prefix, 1 ) if ($ns_uri && $ns_prefix);
996
997     my $string = "";
998
999     # grab the set of matching nodes
1000     my @nodes = $xml->findnodes( $xpath );
1001     for my $value (@nodes) {
1002
1003         # grab all children of the node
1004         my @children = $value->childNodes();
1005         for my $child (@children) {
1006
1007             # add the childs content to the growing buffer
1008             my $content = quotemeta($child->textContent);
1009             next if ($unique && $string =~ /$content/);  # uniquify the values
1010             $string .= $child->textContent . " ";
1011         }
1012         if( ! @children ) {
1013             $string .= $value->textContent . " ";
1014         }
1015     }
1016
1017     $string =~ s/(\w+)\/(\w+)/$1 $2/sgo;
1018     $string =~ s/(\d{4})-(\d{4})/$1 $2/sgo;
1019
1020     return NFD($string);
1021 }
1022
1023 sub class_index_string_xml {
1024     my $self = shift;
1025     my $client = shift;
1026     my $xml = shift;
1027     my @classes = @_;
1028
1029     OpenILS::Application::Ingest->post_init();
1030     $xml = $parser->parse_string(OpenILS::Application::AppUtils->entityize($xml)) unless (ref $xml);
1031
1032     my %transform_cache;
1033     
1034     for my $class (@classes) {
1035         my $class_constructor = "Fieldmapper::metabib::${class}_field_entry";
1036         for my $type ( keys %{ $xpathset->{$class} } ) {
1037
1038             my $def = $xpathset->{$class}->{$type};
1039             my $sf = $OpenILS::Application::Ingest::supported_formats{$def->{format}};
1040
1041             my $document = $xml;
1042
1043             if ($sf->{xslt}) {
1044                 $document = $transform_cache{$def->{format}} || $sf->{xslt}->transform($xml);
1045                 $transform_cache{$def->{format}} = $document;
1046             }
1047
1048             my $value =  xpath_to_string(
1049                     $document->documentElement    => $def->{xpath},
1050                     $sf->{ns}            => $def->{format},
1051                     1
1052             );
1053
1054             next unless $value;
1055
1056             $value = NFD($value);
1057             $value =~ s/\pM+//sgo;
1058             $value =~ s/\pC+//sgo;
1059             $value =~ s/\W+$//sgo;
1060
1061             $value =~ s/\b\.+\b//sgo;
1062             $value = lc($value);
1063
1064             my $fm = $class_constructor->new;
1065             $fm->value( $value );
1066             $fm->field( $xpathset->{$class}->{$type}->{id} );
1067             $client->respond($fm);
1068         }
1069     }
1070     return undef;
1071 }
1072 __PACKAGE__->register_method(  
1073     api_name    => "open-ils.ingest.field_entry.class.xml",
1074     method        => "class_index_string_xml",
1075     api_level    => 1,
1076     argc        => 2,
1077     stream        => 1,
1078 );                      
1079
1080 sub class_index_string_record {
1081     my $self = shift;
1082     my $client = shift;
1083     my $rec = shift;
1084     my @classes = shift;
1085
1086     OpenILS::Application::Ingest->post_init();
1087     my $r = OpenSRF::AppSession
1088             ->create('open-ils.cstore')
1089             ->request( 'open-ils.cstore.direct.authority.record_entry.retrieve' => $rec )
1090             ->gather(1);
1091
1092     return undef unless ($r and @$r);
1093
1094     for my $fm ($self->method_lookup("open-ils.ingest.field_entry.class.xml")->run($r->marc, @classes)) {
1095         $fm->source($rec);
1096         $client->respond($fm);
1097     }
1098     return undef;
1099 }
1100 __PACKAGE__->register_method(  
1101     api_name    => "open-ils.ingest.field_entry.class.record",
1102     method        => "class_index_string_record",
1103     api_level    => 1,
1104     argc        => 2,
1105     stream        => 1,
1106 );                      
1107
1108 sub all_index_string_xml {
1109     my $self = shift;
1110     my $client = shift;
1111     my $xml = shift;
1112
1113     for my $fm ($self->method_lookup("open-ils.ingest.field_entry.class.xml")->run($xml, keys(%$xpathset))) {
1114         $client->respond($fm);
1115     }
1116     return undef;
1117 }
1118 __PACKAGE__->register_method(  
1119     api_name    => "open-ils.ingest.extract.field_entry.all.xml",
1120     method        => "all_index_string_xml",
1121     api_level    => 1,
1122     argc        => 1,
1123     stream        => 1,
1124 );                      
1125
1126 sub all_index_string_record {
1127     my $self = shift;
1128     my $client = shift;
1129     my $rec = shift;
1130
1131     OpenILS::Application::Ingest->post_init();
1132     my $r = OpenSRF::AppSession
1133             ->create('open-ils.cstore')
1134             ->request( 'open-ils.cstore.direct.biblio.record_entry.retrieve' => $rec )
1135             ->gather(1);
1136
1137     return undef unless ($r and @$r);
1138
1139     for my $fm ($self->method_lookup("open-ils.ingest.field_entry.class.xml")->run($r->marc, keys(%$xpathset))) {
1140         $fm->source($rec);
1141         $client->respond($fm);
1142     }
1143     return undef;
1144 }
1145 __PACKAGE__->register_method(  
1146     api_name    => "open-ils.ingest.extract.field_entry.all.record",
1147     method        => "all_index_string_record",
1148     api_level    => 1,
1149     argc        => 1,
1150     stream        => 1,
1151 );                      
1152
1153 # --------------------------------------------------------------------------------
1154 # Flat MARC
1155
1156 package OpenILS::Application::Ingest::FlatMARC;
1157 use base qw/OpenILS::Application::Ingest/;
1158 use Unicode::Normalize;
1159
1160
1161 sub _marcxml_to_full_rows {
1162
1163     my $marcxml = shift;
1164     my $xmltype = shift || 'metabib';
1165
1166     my $type = "Fieldmapper::${xmltype}::full_rec";
1167
1168     my @ns_list;
1169     
1170     my ($root) = $marcxml->findnodes('//*[local-name()="record"]');
1171
1172     for my $tagline ( @{$root->getChildrenByTagName("leader")} ) {
1173         next unless $tagline;
1174
1175         my $ns = $type->new;
1176
1177         $ns->tag( 'LDR' );
1178         my $val = $tagline->textContent;
1179         $val = NFD($val);
1180         $val =~ s/\pM+//sgo;
1181         $val =~ s/\pC+//sgo;
1182         $val =~ s/\W+$//sgo;
1183         $ns->value( $val );
1184
1185         push @ns_list, $ns;
1186     }
1187
1188     for my $tagline ( @{$root->getChildrenByTagName("controlfield")} ) {
1189         next unless $tagline;
1190
1191         my $ns = $type->new;
1192
1193         $ns->tag( $tagline->getAttribute( "tag" ) );
1194         my $val = $tagline->textContent;
1195         $val = NFD($val);
1196         $val =~ s/\pM+//sgo;
1197         $val =~ s/\pC+//sgo;
1198         $val =~ s/\W+$//sgo;
1199         $ns->value( $val );
1200
1201         push @ns_list, $ns;
1202     }
1203
1204     for my $tagline ( @{$root->getChildrenByTagName("datafield")} ) {
1205         next unless $tagline;
1206
1207         my $tag = $tagline->getAttribute( "tag" );
1208         my $ind1 = $tagline->getAttribute( "ind1" );
1209         my $ind2 = $tagline->getAttribute( "ind2" );
1210
1211         for my $data ( @{$tagline->getChildrenByTagName('subfield')} ) {
1212             next unless $data;
1213
1214             my $ns = $type->new;
1215
1216             $ns->tag( $tag );
1217             $ns->ind1( $ind1 );
1218             $ns->ind2( $ind2 );
1219             $ns->subfield( $data->getAttribute( "code" ) );
1220             my $val = $data->textContent;
1221             $val = NFD($val);
1222             $val =~ s/\pM+//sgo;
1223             $val =~ s/\pC+//sgo;
1224             $val =~ s/\W+$//sgo;
1225             $val =~ s/(\d{4})-(\d{4})/$1 $2/sgo;
1226             $val =~ s/(\w+)\/(\w+)/$1 $2/sgo;
1227             $ns->value( lc($val) );
1228
1229             push @ns_list, $ns;
1230         }
1231
1232         if ($xmltype eq 'metabib' and $tag eq '245') {
1233                $tag = 'tnf';
1234     
1235             for my $data ( @{$tagline->getChildrenByTagName('subfield')} ) {
1236                 next unless ($data and $data->getAttribute( "code" ) eq 'a');
1237     
1238                 $ns = $type->new;
1239     
1240                 $ns->tag( $tag );
1241                 $ns->ind1( $ind1 );
1242                 $ns->ind2( $ind2 );
1243                 $ns->subfield( $data->getAttribute( "code" ) );
1244                 my $val = substr( $data->textContent, $ind2 );
1245                 $val = NFD($val);
1246                 $val =~ s/\pM+//sgo;
1247                 $val =~ s/\pC+//sgo;
1248                 $val =~ s/\W+$//sgo;
1249                 $val =~ s/(\w+)\/(\w+)/$1 $2/sgo;
1250                 $val =~ s/(\d{4})-(\d{4})/$1 $2/sgo;
1251                 $ns->value( lc($val) );
1252     
1253                 push @ns_list, $ns;
1254             }
1255         }
1256     }
1257
1258     $log->debug("Returning ".scalar(@ns_list)." Fieldmapper nodes from $xmltype xml");
1259     return @ns_list;
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: