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