1 package OpenILS::Application::SuperCat;
6 # All OpenSRF applications must be based on OpenSRF::Application or
7 # a subclass thereof. Makes sense, eh?
8 use OpenSRF::Application;
9 use base qw/OpenSRF::Application/;
11 # This is the client class, used for connecting to open-ils.storage
12 use OpenSRF::AppSession;
14 # This is an extention of Error.pm that supplies some error types to throw
15 use OpenSRF::EX qw(:try);
17 # This is a helper class for querying the OpenSRF Settings application ...
18 use OpenSRF::Utils::SettingsClient;
20 # ... and here we have the built in logging helper ...
21 use OpenSRF::Utils::Logger qw($logger);
23 # ... and this is our OpenILS object (en|de)coder and psuedo-ORM package.
24 use OpenILS::Utils::Fieldmapper;
27 # We'll be working with XML, so...
30 use Unicode::Normalize;
43 # we need an XML parser
44 $_parser = new XML::LibXML;
47 $_xslt = new XML::LibXSLT;
49 # parse the MODS xslt ...
50 my $mods_xslt = $_parser->parse_file(
51 OpenSRF::Utils::SettingsClient
53 ->config_value( dirs => 'xsl' ).
54 "/MARC21slim2MODS.xsl"
56 # and stash a transformer
57 $record_xslt{mods} = $_xslt->parse_stylesheet( $mods_xslt );
60 # parse the RDFDC xslt ...
61 my $rdfdc_xslt = $_parser->parse_file(
62 OpenSRF::Utils::SettingsClient
64 ->config_value( dirs => 'xsl' ).
65 "/MARC21slim2RDFDC.xsl"
67 # and stash a transformer
68 $record_xslt{rdfdc} = $_xslt->parse_stylesheet( $rdfdc_xslt );
71 # parse the SRWDC xslt ...
72 my $srwdc_xslt = $_parser->parse_file(
73 OpenSRF::Utils::SettingsClient
75 ->config_value( dirs => 'xsl' ).
76 "/MARC21slim2SRWDC.xsl"
78 # and stash a transformer
79 $record_xslt{srwdc} = $_xslt->parse_stylesheet( $srwdc_xslt );
82 # parse the OAIDC xslt ...
83 my $oaidc_xslt = $_parser->parse_file(
84 OpenSRF::Utils::SettingsClient
86 ->config_value( dirs => 'xsl' ).
87 "/MARC21slim2OAIDC.xsl"
89 # and stash a transformer
90 $record_xslt{oaidc} = $_xslt->parse_stylesheet( $oaidc_xslt );
93 # parse the RSS xslt ...
94 my $rss_xslt = $_parser->parse_file(
95 OpenSRF::Utils::SettingsClient
97 ->config_value( dirs => 'xsl' ).
98 "/MARC21slim2RSS2.xsl"
100 # and stash a transformer
101 $record_xslt{rss2} = $_xslt->parse_stylesheet( $rss_xslt );
104 # and finally, a storage server session
105 $_storage = OpenSRF::AppSession->create( 'open-ils.storage' );
107 register_record_transforms();
112 sub register_record_transforms {
113 for my $type ( keys %record_xslt ) {
114 __PACKAGE__->register_method(
115 method => 'retrieve_record_transform',
116 api_name => "open-ils.supercat.record.$type.retrieve",
121 Returns the \U$type\E representation of the requested bibliographic record
126 desc => 'An OpenILS biblio::record_entry id',
130 { desc => "The bib record in \U$type\E",
139 my $stuff = NFC(shift());
140 $stuff =~ s/([\x{0080}-\x{fffd}])/sprintf('&#x%X;',ord($1))/sgoe;
145 sub retrieve_record_marcxml {
153 ->request( 'open-ils.storage.direct.biblio.record_entry.retrieve' => $rid )
159 __PACKAGE__->register_method(
160 method => 'retrieve_record_marcxml',
161 api_name => 'open-ils.supercat.record.marcxml.retrieve',
166 Returns the MARCXML representation of the requested bibliographic record
171 desc => 'An OpenILS biblio::record_entry id',
175 { desc => 'The bib record in MARCXML',
180 sub retrieve_record_transform {
185 (my $transform = $self->api_name) =~ s/^.+record\.([^\.]+)\.retrieve$/$1/o;
187 my $marc = $_storage->request(
188 'open-ils.storage.direct.biblio.record_entry.retrieve',
192 return entityize($record_xslt{$transform}->transform( $_parser->parse_string( $marc ) )->toString);
196 sub retrieve_metarecord_mods {
204 # Get the metarecord in question
207 'open-ils.storage.direct.metabib.metarecord.retrieve' => $rid
210 # Now get the map of all bib records for the metarecord
213 'open-ils.storage.direct.metabib.metarecord_source_map.search.metarecord.atomic',
217 $logger->debug("Adding ".scalar(@$recs)." bib record to the MODS of the metarecord");
219 # and retrieve the lead (master) record as MODS
221 $self ->method_lookup('open-ils.supercat.record.mods.retrieve')
222 ->run($mr->master_record);
223 my $master_mods = $_parser->parse_string($master)->documentElement;
224 $master_mods->setNamespace( "http://www.loc.gov/mods/", "mods", 1 );
226 # ... and a MODS clone to populate, with guts removed.
227 my $mods = $_parser->parse_string($master)->documentElement;
228 $mods->setNamespace( "http://www.loc.gov/mods/", "mods", 1 );
229 ($mods) = $mods->findnodes('//mods:mods');
230 $mods->removeChildNodes;
232 # Add the metarecord ID as a (locally defined) info URI
233 my $recordInfo = $mods
235 ->createElement("mods:recordInfo");
237 my $recordIdentifier = $mods
239 ->createElement("mods:recordIdentifier");
241 $recordIdentifier->setAttribute( source => 'info:oils/metabib-metarecord/' );
244 $recordIdentifier->appendTextNode( $id );
246 $recordInfo->appendChild($recordIdentifier);
247 $mods->appendChild($recordInfo);
249 # Grab the title, author and ISBN for the master record and populate the metarecord
250 my ($title) = $master_mods->findnodes( './mods:titleInfo[not(@type)]' );
253 $title->setNamespace( "http://www.loc.gov/mods/", "mods", 1 );
254 $title = $mods->ownerDocument->importNode($title);
255 $mods->appendChild($title);
258 my ($author) = $master_mods->findnodes( './mods:name[mods:role/mods:text[text()="creator"]]' );
260 $author->setNamespace( "http://www.loc.gov/mods/", "mods", 1 );
261 $author = $mods->ownerDocument->importNode($author);
262 $mods->appendChild($author);
265 my ($isbn) = $master_mods->findnodes( './mods:identifier[@type="isbn"]' );
267 $isbn->setNamespace( "http://www.loc.gov/mods/", "mods", 1 );
268 $isbn = $mods->ownerDocument->importNode($isbn);
269 $mods->appendChild($isbn);
272 # ... and loop over the constituent records
273 for my $map ( @$recs ) {
277 $self ->method_lookup('open-ils.supercat.record.mods.retrieve')
280 my $part_mods = $_parser->parse_string($rec);
281 $part_mods->documentElement->setNamespace( "http://www.loc.gov/mods/", "mods", 1 );
282 ($part_mods) = $part_mods->findnodes('//mods:mods');
284 for my $node ( ($part_mods->findnodes( './mods:subject' )) ) {
285 $node->setNamespace( "http://www.loc.gov/mods/", "mods", 1 );
286 $node = $mods->ownerDocument->importNode($node);
287 $mods->appendChild( $node );
290 my $relatedItem = $mods
292 ->createElement("mods:relatedItem");
294 $relatedItem->setAttribute( type => 'constituent' );
296 my $identifier = $mods
298 ->createElement("mods:identifier");
300 $identifier->setAttribute( type => 'uri' );
302 my $subRecordInfo = $mods
304 ->createElement("mods:recordInfo");
306 my $subRecordIdentifier = $mods
308 ->createElement("mods:recordIdentifier");
310 $subRecordIdentifier->setAttribute( source => 'info:oils/biblio-record_entry/' );
312 my $subid = $map->source;
313 $subRecordIdentifier->appendTextNode( $subid );
314 $subRecordInfo->appendChild($subRecordIdentifier);
316 $relatedItem->appendChild( $subRecordInfo );
318 my ($tor) = $part_mods->findnodes( './mods:typeOfResource' );
319 $tor->setNamespace( "http://www.loc.gov/mods/", "mods", 1 ) if ($tor);
320 $tor = $mods->ownerDocument->importNode($tor) if ($tor);
321 $relatedItem->appendChild($tor) if ($tor);
323 if ( my ($part_isbn) = $part_mods->findnodes( './mods:identifier[@type="isbn"]' ) ) {
324 $part_isbn->setNamespace( "http://www.loc.gov/mods/", "mods", 1 );
325 $part_isbn = $mods->ownerDocument->importNode($part_isbn);
326 $relatedItem->appendChild( $part_isbn );
329 $isbn = $mods->appendChild( $part_isbn->cloneNode(1) );
333 $mods->appendChild( $relatedItem );
337 $_storage->disconnect;
339 return entityize($mods->toString);
342 __PACKAGE__->register_method(
343 method => 'retrieve_metarecord_mods',
344 api_name => 'open-ils.supercat.metarecord.mods.retrieve',
349 Returns the MODS representation of the requested metarecord
353 { name => 'metarecordId',
354 desc => 'An OpenILS metabib::metarecord id',
358 { desc => 'The metarecord in MODS',
363 sub list_metarecord_formats {
364 return ['mods', keys %metarecord_xslt];
366 __PACKAGE__->register_method(
367 method => 'list_metarecord_formats',
368 api_name => 'open-ils.supercat.metarecord.formats',
373 Returns the list of valid metarecord formats that supercat understands.
376 { desc => 'The format list',
382 sub list_record_formats {
383 return ['marcxml', keys %record_xslt];
385 __PACKAGE__->register_method(
386 method => 'list_record_formats',
387 api_name => 'open-ils.supercat.record.formats',
392 Returns the list of valid record formats that supercat understands.
395 { desc => 'The format list',
406 throw OpenSRF::EX::InvalidArg ('I need an ISBN please')
407 unless (length($isbn) >= 10);
409 # Create a storage session, since we'll be making muliple requests.
412 # Find the record that has that ISBN.
413 my $bibrec = $_storage->request(
414 'open-ils.storage.direct.metabib.full_rec.search_where.atomic',
415 { tag => '020', subfield => 'a', value => { like => $isbn.'%'} }
418 # Go away if we don't have one.
419 return {} unless (@$bibrec);
421 # Find the metarecord for that bib record.
422 my $mr = $_storage->request(
423 'open-ils.storage.direct.metabib.metarecord_source_map.search.source.atomic',
427 # Find the other records for that metarecord.
428 my $records = $_storage->request(
429 'open-ils.storage.direct.metabib.metarecord_source_map.search.metarecord.atomic',
433 # Just to be safe. There's currently no unique constraint on sources...
434 my %unique_recs = map { ($_->source, 1) } @$records;
435 my @rec_list = sort keys %unique_recs;
437 # And now fetch the ISBNs for thos records.
438 my $recs = $_storage->request(
439 'open-ils.storage.direct.metabib.full_rec.search_where.atomic',
440 { tag => '020', subfield => 'a', record => \@rec_list }
443 # We're done with the storage server session.
444 $_storage->disconnect;
446 # Return the oISBN data structure. This will be XMLized at a higher layer.
448 { metarecord => $mr->[0]->metarecord,
449 record_list => { map { ($_->record, $_->value) } @$recs } };
452 __PACKAGE__->register_method(
454 api_name => 'open-ils.supercat.oisbn',
459 Returns the ISBN list for the metarecord of the requested isbn
464 desc => 'An ISBN. Duh.',
468 { desc => 'record to isbn map',