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 $mods_xslt = $_parser->parse_file(
53 OpenSRF::Utils::SettingsClient
55 ->config_value( dirs => 'xsl' ).
56 "/MARC21slim2MODS.xsl"
58 # and stash a transformer
59 $record_xslt{mods}{xslt} = $_xslt->parse_stylesheet( $mods_xslt );
60 $record_xslt{mods}{namespace_uri} = 'http://www.loc.gov/mods/';
61 $record_xslt{mods}{docs} = 'http://www.loc.gov/mods/';
62 $record_xslt{mods}{schema_location} = 'http://www.loc.gov/standards/mods/mods.xsd';
64 $logger->debug("Got here!");
66 # parse the ATOM entry xslt ...
67 my $atom_xslt = $_parser->parse_file(
68 OpenSRF::Utils::SettingsClient
70 ->config_value( dirs => 'xsl' ).
71 "/MARC21slim2ATOM.xsl"
73 # and stash a transformer
74 $record_xslt{atom}{xslt} = $_xslt->parse_stylesheet( $atom_xslt );
75 $record_xslt{atom}{namespace_uri} = 'http://www.w3.org/2005/Atom';
76 $record_xslt{atom}{docs} = 'http://www.ietf.org/rfc/rfc4287.txt';
78 # parse the RDFDC xslt ...
79 my $rdf_dc_xslt = $_parser->parse_file(
80 OpenSRF::Utils::SettingsClient
82 ->config_value( dirs => 'xsl' ).
83 "/MARC21slim2RDFDC.xsl"
85 # and stash a transformer
86 $record_xslt{rdf_dc}{xslt} = $_xslt->parse_stylesheet( $rdf_dc_xslt );
87 $record_xslt{rdf_dc}{namespace_uri} = 'http://purl.org/dc/elements/1.1/';
88 $record_xslt{rdf_dc}{schema_location} = 'http://purl.org/dc/elements/1.1/';
90 $logger->debug("Got here!");
92 # parse the SRWDC xslt ...
93 my $srw_dc_xslt = $_parser->parse_file(
94 OpenSRF::Utils::SettingsClient
96 ->config_value( dirs => 'xsl' ).
97 "/MARC21slim2SRWDC.xsl"
99 # and stash a transformer
100 $record_xslt{srw_dc}{xslt} = $_xslt->parse_stylesheet( $srw_dc_xslt );
101 $record_xslt{srw_dc}{namespace_uri} = 'info:srw/schema/1/dc-schema';
102 $record_xslt{srw_dc}{schema_location} = 'http://www.loc.gov/z3950/agency/zing/srw/dc-schema.xsd';
104 $logger->debug("Got here!");
106 # parse the OAIDC xslt ...
107 my $oai_dc_xslt = $_parser->parse_file(
108 OpenSRF::Utils::SettingsClient
110 ->config_value( dirs => 'xsl' ).
111 "/MARC21slim2OAIDC.xsl"
113 # and stash a transformer
114 $record_xslt{oai_dc}{xslt} = $_xslt->parse_stylesheet( $oai_dc_xslt );
115 $record_xslt{oai_dc}{namespace_uri} = 'http://www.openarchives.org/OAI/2.0/oai_dc/';
116 $record_xslt{oai_dc}{schema_location} = 'http://www.openarchives.org/OAI/2.0/oai_dc.xsd';
118 $logger->debug("Got here!");
120 # parse the RSS xslt ...
121 my $rss_xslt = $_parser->parse_file(
122 OpenSRF::Utils::SettingsClient
124 ->config_value( dirs => 'xsl' ).
125 "/MARC21slim2RSS2.xsl"
127 # and stash a transformer
128 $record_xslt{rss2}{xslt} = $_xslt->parse_stylesheet( $rss_xslt );
130 $logger->debug("Got here!");
132 # and finally, a storage server session
133 $_storage = OpenSRF::AppSession->create( 'open-ils.storage' );
135 register_record_transforms();
140 sub register_record_transforms {
141 for my $type ( keys %record_xslt ) {
142 __PACKAGE__->register_method(
143 method => 'retrieve_record_transform',
144 api_name => "open-ils.supercat.record.$type.retrieve",
149 Returns the \U$type\E representation of the requested bibliographic record
154 desc => 'An OpenILS biblio::record_entry id',
158 { desc => "The bib record in \U$type\E",
167 my $stuff = NFC(shift());
168 $stuff =~ s/([\x{0080}-\x{fffd}])/sprintf('&#x%X;',ord($1))/sgoe;
173 sub retrieve_record_marcxml {
181 ->request( 'open-ils.storage.direct.biblio.record_entry.retrieve' => $rid )
187 __PACKAGE__->register_method(
188 method => 'retrieve_record_marcxml',
189 api_name => 'open-ils.supercat.record.marcxml.retrieve',
194 Returns the MARCXML representation of the requested bibliographic record
199 desc => 'An OpenILS biblio::record_entry id',
203 { desc => 'The bib record in MARCXML',
208 sub retrieve_record_transform {
213 (my $transform = $self->api_name) =~ s/^.+record\.([^\.]+)\.retrieve$/$1/o;
215 my $marc = $_storage->request(
216 'open-ils.storage.direct.biblio.record_entry.retrieve',
220 return entityize($record_xslt{$transform}{xslt}->transform( $_parser->parse_string( $marc ) )->toString);
224 sub retrieve_metarecord_mods {
232 # Get the metarecord in question
235 'open-ils.storage.direct.metabib.metarecord.retrieve' => $rid
238 # Now get the map of all bib records for the metarecord
241 'open-ils.storage.direct.metabib.metarecord_source_map.search.metarecord.atomic',
245 $logger->debug("Adding ".scalar(@$recs)." bib record to the MODS of the metarecord");
247 # and retrieve the lead (master) record as MODS
249 $self ->method_lookup('open-ils.supercat.record.mods.retrieve')
250 ->run($mr->master_record);
251 my $master_mods = $_parser->parse_string($master)->documentElement;
252 $master_mods->setNamespace( "http://www.loc.gov/mods/", "mods", 1 );
254 # ... and a MODS clone to populate, with guts removed.
255 my $mods = $_parser->parse_string($master)->documentElement;
256 $mods->setNamespace( "http://www.loc.gov/mods/", "mods", 1 );
257 ($mods) = $mods->findnodes('//mods:mods');
258 $mods->removeChildNodes;
260 # Add the metarecord ID as a (locally defined) info URI
261 my $recordInfo = $mods
263 ->createElement("mods:recordInfo");
265 my $recordIdentifier = $mods
267 ->createElement("mods:recordIdentifier");
269 my ($year,$month,$day) = reverse( (localtime)[3,4,5] );
274 $recordIdentifier->appendTextNode(
275 sprintf("tag:open-ils.org,$year-\%0.2d-\%0.2d:biblio-record_entry/$id",
281 $recordInfo->appendChild($recordIdentifier);
282 $mods->appendChild($recordInfo);
284 # Grab the title, author and ISBN for the master record and populate the metarecord
285 my ($title) = $master_mods->findnodes( './mods:titleInfo[not(@type)]' );
288 $title->setNamespace( "http://www.loc.gov/mods/", "mods", 1 );
289 $title = $mods->ownerDocument->importNode($title);
290 $mods->appendChild($title);
293 my ($author) = $master_mods->findnodes( './mods:name[mods:role/mods:text[text()="creator"]]' );
295 $author->setNamespace( "http://www.loc.gov/mods/", "mods", 1 );
296 $author = $mods->ownerDocument->importNode($author);
297 $mods->appendChild($author);
300 my ($isbn) = $master_mods->findnodes( './mods:identifier[@type="isbn"]' );
302 $isbn->setNamespace( "http://www.loc.gov/mods/", "mods", 1 );
303 $isbn = $mods->ownerDocument->importNode($isbn);
304 $mods->appendChild($isbn);
307 # ... and loop over the constituent records
308 for my $map ( @$recs ) {
312 $self ->method_lookup('open-ils.supercat.record.mods.retrieve')
315 my $part_mods = $_parser->parse_string($rec);
316 $part_mods->documentElement->setNamespace( "http://www.loc.gov/mods/", "mods", 1 );
317 ($part_mods) = $part_mods->findnodes('//mods:mods');
319 for my $node ( ($part_mods->findnodes( './mods:subject' )) ) {
320 $node->setNamespace( "http://www.loc.gov/mods/", "mods", 1 );
321 $node = $mods->ownerDocument->importNode($node);
322 $mods->appendChild( $node );
325 my $relatedItem = $mods
327 ->createElement("mods:relatedItem");
329 $relatedItem->setAttribute( type => 'constituent' );
331 my $identifier = $mods
333 ->createElement("mods:identifier");
335 $identifier->setAttribute( type => 'uri' );
337 my $subRecordInfo = $mods
339 ->createElement("mods:recordInfo");
341 my $subRecordIdentifier = $mods
343 ->createElement("mods:recordIdentifier");
345 my $subid = $map->source;
346 $subRecordIdentifier->appendTextNode(
347 sprintf("tag:open-ils.org,$year-\%0.2d-\%0.2d:biblio-record_entry/$subid",
352 $subRecordInfo->appendChild($subRecordIdentifier);
354 $relatedItem->appendChild( $subRecordInfo );
356 my ($tor) = $part_mods->findnodes( './mods:typeOfResource' );
357 $tor->setNamespace( "http://www.loc.gov/mods/", "mods", 1 ) if ($tor);
358 $tor = $mods->ownerDocument->importNode($tor) if ($tor);
359 $relatedItem->appendChild($tor) if ($tor);
361 if ( my ($part_isbn) = $part_mods->findnodes( './mods:identifier[@type="isbn"]' ) ) {
362 $part_isbn->setNamespace( "http://www.loc.gov/mods/", "mods", 1 );
363 $part_isbn = $mods->ownerDocument->importNode($part_isbn);
364 $relatedItem->appendChild( $part_isbn );
367 $isbn = $mods->appendChild( $part_isbn->cloneNode(1) );
371 $mods->appendChild( $relatedItem );
375 $_storage->disconnect;
377 return entityize($mods->toString);
380 __PACKAGE__->register_method(
381 method => 'retrieve_metarecord_mods',
382 api_name => 'open-ils.supercat.metarecord.mods.retrieve',
387 Returns the MODS representation of the requested metarecord
391 { name => 'metarecordId',
392 desc => 'An OpenILS metabib::metarecord id',
396 { desc => 'The metarecord in MODS',
401 sub list_metarecord_formats {
404 { namespace_uri => 'http://www.loc.gov/mods/',
405 docs => 'http://www.loc.gov/mods/',
406 schema_location => 'http://www.loc.gov/standards/mods/mods.xsd',
411 for my $type ( keys %metarecord_xslt ) {
414 { namespace_uri => $metarecord_xslt{$type}{namespace_uri},
415 docs => $metarecord_xslt{$type}{docs},
416 schema_location => $metarecord_xslt{$type}{schema_location},
423 __PACKAGE__->register_method(
424 method => 'list_metarecord_formats',
425 api_name => 'open-ils.supercat.metarecord.formats',
430 Returns the list of valid metarecord formats that supercat understands.
433 { desc => 'The format list',
439 sub list_record_formats {
442 { namespace_uri => 'http://www.loc.gov/MARC21/slim',
443 docs => 'http://www.loc.gov/marcxml/',
444 schema_location => 'http://www.loc.gov/standards/marcxml/schema/MARC21slim.xsd',
449 for my $type ( keys %record_xslt ) {
452 { namespace_uri => $record_xslt{$type}{namespace_uri},
453 docs => $record_xslt{$type}{docs},
454 schema_location => $record_xslt{$type}{schema_location},
461 __PACKAGE__->register_method(
462 method => 'list_record_formats',
463 api_name => 'open-ils.supercat.record.formats',
468 Returns the list of valid record formats that supercat understands.
471 { desc => 'The format list',
482 throw OpenSRF::EX::InvalidArg ('I need an ISBN please')
483 unless (length($isbn) >= 10);
485 # Create a storage session, since we'll be making muliple requests.
488 # Find the record that has that ISBN.
489 my $bibrec = $_storage->request(
490 'open-ils.storage.direct.metabib.full_rec.search_where.atomic',
491 { tag => '020', subfield => 'a', value => { ilike => $isbn.'%'} }
494 # Go away if we don't have one.
495 return {} unless (@$bibrec);
497 # Find the metarecord for that bib record.
498 my $mr = $_storage->request(
499 'open-ils.storage.direct.metabib.metarecord_source_map.search.source.atomic',
503 # Find the other records for that metarecord.
504 my $records = $_storage->request(
505 'open-ils.storage.direct.metabib.metarecord_source_map.search.metarecord.atomic',
509 # Just to be safe. There's currently no unique constraint on sources...
510 my %unique_recs = map { ($_->source, 1) } @$records;
511 my @rec_list = sort keys %unique_recs;
513 # And now fetch the ISBNs for thos records.
514 my $recs = $_storage->request(
515 'open-ils.storage.direct.metabib.full_rec.search_where.atomic',
516 { tag => '020', subfield => 'a', record => \@rec_list }
519 # We're done with the storage server session.
520 $_storage->disconnect;
522 # Return the oISBN data structure. This will be XMLized at a higher layer.
524 { metarecord => $mr->[0]->metarecord,
525 record_list => { map { ($_->record, $_->value) } @$recs } };
528 __PACKAGE__->register_method(
530 api_name => 'open-ils.supercat.oisbn',
535 Returns the ISBN list for the metarecord of the requested isbn
540 desc => 'An ISBN. Duh.',
544 { desc => 'record to isbn map',