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;
46 $logger->debug("Got here!");
49 $_xslt = new XML::LibXSLT;
51 # parse the MODS xslt ...
52 my $mods3_xslt = $_parser->parse_file(
53 OpenSRF::Utils::SettingsClient
55 ->config_value( dirs => 'xsl' ).
56 "/MARC21slim2MODS3.xsl"
58 # and stash a transformer
59 $record_xslt{mods3}{xslt} = $_xslt->parse_stylesheet( $mods3_xslt );
60 $record_xslt{mods3}{namespace_uri} = 'http://www.loc.gov/mods/v3';
61 $record_xslt{mods3}{docs} = 'http://www.loc.gov/mods/';
62 $record_xslt{mods3}{schema_location} = 'http://www.loc.gov/standards/mods/v3/mods-3-1.xsd';
64 # parse the MODS xslt ...
65 my $mods_xslt = $_parser->parse_file(
66 OpenSRF::Utils::SettingsClient
68 ->config_value( dirs => 'xsl' ).
69 "/MARC21slim2MODS.xsl"
71 # and stash a transformer
72 $record_xslt{mods}{xslt} = $_xslt->parse_stylesheet( $mods_xslt );
73 $record_xslt{mods}{namespace_uri} = 'http://www.loc.gov/mods/';
74 $record_xslt{mods}{docs} = 'http://www.loc.gov/mods/';
75 $record_xslt{mods}{schema_location} = 'http://www.loc.gov/standards/mods/mods.xsd';
77 $logger->debug("Got here!");
79 # parse the ATOM entry xslt ...
80 my $atom_xslt = $_parser->parse_file(
81 OpenSRF::Utils::SettingsClient
83 ->config_value( dirs => 'xsl' ).
84 "/MARC21slim2ATOM.xsl"
86 # and stash a transformer
87 $record_xslt{atom}{xslt} = $_xslt->parse_stylesheet( $atom_xslt );
88 $record_xslt{atom}{namespace_uri} = 'http://www.w3.org/2005/Atom';
89 $record_xslt{atom}{docs} = 'http://www.ietf.org/rfc/rfc4287.txt';
91 # parse the RDFDC xslt ...
92 my $rdf_dc_xslt = $_parser->parse_file(
93 OpenSRF::Utils::SettingsClient
95 ->config_value( dirs => 'xsl' ).
96 "/MARC21slim2RDFDC.xsl"
98 # and stash a transformer
99 $record_xslt{rdf_dc}{xslt} = $_xslt->parse_stylesheet( $rdf_dc_xslt );
100 $record_xslt{rdf_dc}{namespace_uri} = 'http://purl.org/dc/elements/1.1/';
101 $record_xslt{rdf_dc}{schema_location} = 'http://purl.org/dc/elements/1.1/';
103 $logger->debug("Got here!");
105 # parse the SRWDC xslt ...
106 my $srw_dc_xslt = $_parser->parse_file(
107 OpenSRF::Utils::SettingsClient
109 ->config_value( dirs => 'xsl' ).
110 "/MARC21slim2SRWDC.xsl"
112 # and stash a transformer
113 $record_xslt{srw_dc}{xslt} = $_xslt->parse_stylesheet( $srw_dc_xslt );
114 $record_xslt{srw_dc}{namespace_uri} = 'info:srw/schema/1/dc-schema';
115 $record_xslt{srw_dc}{schema_location} = 'http://www.loc.gov/z3950/agency/zing/srw/dc-schema.xsd';
117 $logger->debug("Got here!");
119 # parse the OAIDC xslt ...
120 my $oai_dc_xslt = $_parser->parse_file(
121 OpenSRF::Utils::SettingsClient
123 ->config_value( dirs => 'xsl' ).
124 "/MARC21slim2OAIDC.xsl"
126 # and stash a transformer
127 $record_xslt{oai_dc}{xslt} = $_xslt->parse_stylesheet( $oai_dc_xslt );
128 $record_xslt{oai_dc}{namespace_uri} = 'http://www.openarchives.org/OAI/2.0/oai_dc/';
129 $record_xslt{oai_dc}{schema_location} = 'http://www.openarchives.org/OAI/2.0/oai_dc.xsd';
131 $logger->debug("Got here!");
133 # parse the RSS xslt ...
134 my $rss_xslt = $_parser->parse_file(
135 OpenSRF::Utils::SettingsClient
137 ->config_value( dirs => 'xsl' ).
138 "/MARC21slim2RSS2.xsl"
140 # and stash a transformer
141 $record_xslt{rss2}{xslt} = $_xslt->parse_stylesheet( $rss_xslt );
143 $logger->debug("Got here!");
145 # and finally, a storage server session
146 $_storage = OpenSRF::AppSession->create( 'open-ils.storage' );
148 register_record_transforms();
153 sub register_record_transforms {
154 for my $type ( keys %record_xslt ) {
155 __PACKAGE__->register_method(
156 method => 'retrieve_record_transform',
157 api_name => "open-ils.supercat.record.$type.retrieve",
162 Returns the \U$type\E representation of the requested bibliographic record
167 desc => 'An OpenILS biblio::record_entry id',
171 { desc => "The bib record in \U$type\E",
180 my $stuff = NFC(shift());
181 $stuff =~ s/([\x{0080}-\x{fffd}])/sprintf('&#x%X;',ord($1))/sgoe;
186 sub retrieve_record_marcxml {
194 ->request( 'open-ils.storage.direct.biblio.record_entry.retrieve' => $rid )
200 __PACKAGE__->register_method(
201 method => 'retrieve_record_marcxml',
202 api_name => 'open-ils.supercat.record.marcxml.retrieve',
207 Returns the MARCXML representation of the requested bibliographic record
212 desc => 'An OpenILS biblio::record_entry id',
216 { desc => 'The bib record in MARCXML',
221 sub retrieve_record_transform {
226 (my $transform = $self->api_name) =~ s/^.+record\.([^\.]+)\.retrieve$/$1/o;
228 my $marc = $_storage->request(
229 'open-ils.storage.direct.biblio.record_entry.retrieve',
233 return entityize($record_xslt{$transform}{xslt}->transform( $_parser->parse_string( $marc ) )->toString);
237 sub retrieve_metarecord_mods {
245 # Get the metarecord in question
248 'open-ils.storage.direct.metabib.metarecord.retrieve' => $rid
251 # Now get the map of all bib records for the metarecord
254 'open-ils.storage.direct.metabib.metarecord_source_map.search.metarecord.atomic',
258 $logger->debug("Adding ".scalar(@$recs)." bib record to the MODS of the metarecord");
260 # and retrieve the lead (master) record as MODS
262 $self ->method_lookup('open-ils.supercat.record.mods.retrieve')
263 ->run($mr->master_record);
264 my $master_mods = $_parser->parse_string($master)->documentElement;
265 $master_mods->setNamespace( "http://www.loc.gov/mods/", "mods", 1 );
267 # ... and a MODS clone to populate, with guts removed.
268 my $mods = $_parser->parse_string($master)->documentElement;
269 $mods->setNamespace( "http://www.loc.gov/mods/", "mods", 1 );
270 ($mods) = $mods->findnodes('//mods:mods');
271 $mods->removeChildNodes;
273 # Add the metarecord ID as a (locally defined) info URI
274 my $recordInfo = $mods
276 ->createElement("mods:recordInfo");
278 my $recordIdentifier = $mods
280 ->createElement("mods:recordIdentifier");
282 my ($year,$month,$day) = reverse( (localtime)[3,4,5] );
287 $recordIdentifier->appendTextNode(
288 sprintf("tag:open-ils.org,$year-\%0.2d-\%0.2d:biblio-record_entry/$id",
294 $recordInfo->appendChild($recordIdentifier);
295 $mods->appendChild($recordInfo);
297 # Grab the title, author and ISBN for the master record and populate the metarecord
298 my ($title) = $master_mods->findnodes( './mods:titleInfo[not(@type)]' );
301 $title->setNamespace( "http://www.loc.gov/mods/", "mods", 1 );
302 $title = $mods->ownerDocument->importNode($title);
303 $mods->appendChild($title);
306 my ($author) = $master_mods->findnodes( './mods:name[mods:role/mods:text[text()="creator"]]' );
308 $author->setNamespace( "http://www.loc.gov/mods/", "mods", 1 );
309 $author = $mods->ownerDocument->importNode($author);
310 $mods->appendChild($author);
313 my ($isbn) = $master_mods->findnodes( './mods:identifier[@type="isbn"]' );
315 $isbn->setNamespace( "http://www.loc.gov/mods/", "mods", 1 );
316 $isbn = $mods->ownerDocument->importNode($isbn);
317 $mods->appendChild($isbn);
320 # ... and loop over the constituent records
321 for my $map ( @$recs ) {
325 $self ->method_lookup('open-ils.supercat.record.mods.retrieve')
328 my $part_mods = $_parser->parse_string($rec);
329 $part_mods->documentElement->setNamespace( "http://www.loc.gov/mods/", "mods", 1 );
330 ($part_mods) = $part_mods->findnodes('//mods:mods');
332 for my $node ( ($part_mods->findnodes( './mods:subject' )) ) {
333 $node->setNamespace( "http://www.loc.gov/mods/", "mods", 1 );
334 $node = $mods->ownerDocument->importNode($node);
335 $mods->appendChild( $node );
338 my $relatedItem = $mods
340 ->createElement("mods:relatedItem");
342 $relatedItem->setAttribute( type => 'constituent' );
344 my $identifier = $mods
346 ->createElement("mods:identifier");
348 $identifier->setAttribute( type => 'uri' );
350 my $subRecordInfo = $mods
352 ->createElement("mods:recordInfo");
354 my $subRecordIdentifier = $mods
356 ->createElement("mods:recordIdentifier");
358 my $subid = $map->source;
359 $subRecordIdentifier->appendTextNode(
360 sprintf("tag:open-ils.org,$year-\%0.2d-\%0.2d:biblio-record_entry/$subid",
365 $subRecordInfo->appendChild($subRecordIdentifier);
367 $relatedItem->appendChild( $subRecordInfo );
369 my ($tor) = $part_mods->findnodes( './mods:typeOfResource' );
370 $tor->setNamespace( "http://www.loc.gov/mods/", "mods", 1 ) if ($tor);
371 $tor = $mods->ownerDocument->importNode($tor) if ($tor);
372 $relatedItem->appendChild($tor) if ($tor);
374 if ( my ($part_isbn) = $part_mods->findnodes( './mods:identifier[@type="isbn"]' ) ) {
375 $part_isbn->setNamespace( "http://www.loc.gov/mods/", "mods", 1 );
376 $part_isbn = $mods->ownerDocument->importNode($part_isbn);
377 $relatedItem->appendChild( $part_isbn );
380 $isbn = $mods->appendChild( $part_isbn->cloneNode(1) );
384 $mods->appendChild( $relatedItem );
388 $_storage->disconnect;
390 return entityize($mods->toString);
393 __PACKAGE__->register_method(
394 method => 'retrieve_metarecord_mods',
395 api_name => 'open-ils.supercat.metarecord.mods.retrieve',
400 Returns the MODS representation of the requested metarecord
404 { name => 'metarecordId',
405 desc => 'An OpenILS metabib::metarecord id',
409 { desc => 'The metarecord in MODS',
414 sub list_metarecord_formats {
417 { namespace_uri => 'http://www.loc.gov/mods/',
418 docs => 'http://www.loc.gov/mods/',
419 schema_location => 'http://www.loc.gov/standards/mods/mods.xsd',
424 for my $type ( keys %metarecord_xslt ) {
427 { namespace_uri => $metarecord_xslt{$type}{namespace_uri},
428 docs => $metarecord_xslt{$type}{docs},
429 schema_location => $metarecord_xslt{$type}{schema_location},
436 __PACKAGE__->register_method(
437 method => 'list_metarecord_formats',
438 api_name => 'open-ils.supercat.metarecord.formats',
443 Returns the list of valid metarecord formats that supercat understands.
446 { desc => 'The format list',
452 sub list_record_formats {
455 { namespace_uri => 'http://www.loc.gov/MARC21/slim',
456 docs => 'http://www.loc.gov/marcxml/',
457 schema_location => 'http://www.loc.gov/standards/marcxml/schema/MARC21slim.xsd',
462 for my $type ( keys %record_xslt ) {
465 { namespace_uri => $record_xslt{$type}{namespace_uri},
466 docs => $record_xslt{$type}{docs},
467 schema_location => $record_xslt{$type}{schema_location},
474 __PACKAGE__->register_method(
475 method => 'list_record_formats',
476 api_name => 'open-ils.supercat.record.formats',
481 Returns the list of valid record formats that supercat understands.
484 { desc => 'The format list',
495 throw OpenSRF::EX::InvalidArg ('I need an ISBN please')
496 unless (length($isbn) >= 10);
498 # Create a storage session, since we'll be making muliple requests.
501 # Find the record that has that ISBN.
502 my $bibrec = $_storage->request(
503 'open-ils.storage.direct.metabib.full_rec.search_where.atomic',
504 { tag => '020', subfield => 'a', value => { ilike => $isbn.'%'} }
507 # Go away if we don't have one.
508 return {} unless (@$bibrec);
510 # Find the metarecord for that bib record.
511 my $mr = $_storage->request(
512 'open-ils.storage.direct.metabib.metarecord_source_map.search.source.atomic',
516 # Find the other records for that metarecord.
517 my $records = $_storage->request(
518 'open-ils.storage.direct.metabib.metarecord_source_map.search.metarecord.atomic',
522 # Just to be safe. There's currently no unique constraint on sources...
523 my %unique_recs = map { ($_->source, 1) } @$records;
524 my @rec_list = sort keys %unique_recs;
526 # And now fetch the ISBNs for thos records.
527 my $recs = $_storage->request(
528 'open-ils.storage.direct.metabib.full_rec.search_where.atomic',
529 { tag => '020', subfield => 'a', record => \@rec_list }
532 # We're done with the storage server session.
533 $_storage->disconnect;
535 # Return the oISBN data structure. This will be XMLized at a higher layer.
537 { metarecord => $mr->[0]->metarecord,
538 record_list => { map { ($_->record, $_->value) } @$recs } };
541 __PACKAGE__->register_method(
543 api_name => 'open-ils.supercat.oisbn',
548 Returns the ISBN list for the metarecord of the requested isbn
553 desc => 'An ISBN. Duh.',
557 { desc => 'record to isbn map',