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