4920453906d7f1bc17ef50725502bbd6f65be4f5
[Evergreen.git] / Open-ILS / src / perlmods / OpenILS / Application / SuperCat.pm
1 package OpenILS::Application::SuperCat;
2
3 use strict;
4 use warnings;
5
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/;
10
11 # This is the client class, used for connecting to open-ils.storage
12 use OpenSRF::AppSession;
13
14 # This is an extention of Error.pm that supplies some error types to throw
15 use OpenSRF::EX qw(:try);
16
17 # This is a helper class for querying the OpenSRF Settings application ...
18 use OpenSRF::Utils::SettingsClient;
19
20 # ... and here we have the built in logging helper ...
21 use OpenSRF::Utils::Logger qw($logger);
22
23 # ... and this is our OpenILS object (en|de)coder and psuedo-ORM package.
24 use OpenILS::Utils::Fieldmapper;
25
26
27 # We'll be working with XML, so...
28 use XML::LibXML;
29 use XML::LibXSLT;
30 use Unicode::Normalize;
31
32 use JSON;
33
34 our ($_parser, $_mods_sheet, $_storage);
35
36 sub child_init {
37         $_parser = new XML::LibXML;
38         my $mods_xslt = $_parser->parse_file(
39                 OpenSRF::Utils::SettingsClient
40                         ->new
41                         ->config_value( dirs => 'xsl' ).
42                 "/MARC21slim2MODS.xsl"
43         );
44         my $xslt = new XML::LibXSLT;
45         $_mods_sheet = $xslt->parse_stylesheet( $mods_xslt );
46         $_storage = OpenSRF::AppSession->create( 'open-ils.storage' );
47         return 1;
48 }
49
50 sub entityize {
51         my $stuff = NFC(shift());
52         $stuff =~ s/([\x{0080}-\x{fffd}])/sprintf('&#x%X;',ord($1))/sgoe;
53         return $stuff;
54 }
55
56
57 sub retrieve_record_marcxml {
58         my $self = shift;
59         my $client = shift;
60         my $rid = shift;
61
62         return
63         entityize(
64                 $_storage
65                         ->request( 'open-ils.storage.direct.biblio.record_entry.retrieve' => $rid )
66                         ->gather(1)
67                         ->marc
68         );
69 }
70
71 __PACKAGE__->register_method(
72         method    => 'retrieve_record_marcxml',
73         api_name  => 'open-ils.supercat.record.marcxml.retrieve',
74         api_level => 1,
75         argc      => 1,
76         signature =>
77                 { desc     => <<"                 DESC",
78 Returns the MARCXML representation of the requested bibliographic record
79                   DESC
80                   params   =>
81                         [
82                                 { name => 'bibId',
83                                   desc => 'An OpenILS biblio::record_entry id',
84                                   type => 'number' },
85                         ],
86                   'return' =>
87                         { desc => 'The bib record in MARCXML',
88                           type => 'string' }
89                 }
90 );
91
92 sub retrieve_record_mods {
93         my $self = shift;
94         my $client = shift;
95         my $rid = shift;
96
97         my $marc = $_storage->request(
98                 'open-ils.storage.direct.biblio.record_entry.retrieve',
99                 $rid
100         )->gather(1)->marc;
101
102         return entityize($_mods_sheet->transform( $_parser->parse_string( $marc ) )->toString);
103 }
104
105 __PACKAGE__->register_method(
106         method    => 'retrieve_record_mods',
107         api_name  => 'open-ils.supercat.record.mods.retrieve',
108         api_level => 1,
109         argc      => 1,
110         signature =>
111                 { desc     => <<"                 DESC",
112 Returns the MODS representation of the requested bibliographic record
113                   DESC
114                   params   =>
115                         [
116                                 { name => 'bibId',
117                                   desc => 'An OpenILS biblio::record_entry id',
118                                   type => 'number' },
119                         ],
120                   'return' =>
121                         { desc => 'The bib record in MODS',
122                           type => 'string' }
123                 }
124 );
125
126 sub retrieve_metarecord_mods {
127         my $self = shift;
128         my $client = shift;
129         my $rid = shift;
130
131         # We want a session
132         $_storage->connect;
133
134         # Get the metarecord in question
135         my $mr =
136         $_storage->request(
137                 'open-ils.storage.direct.metabib.metarecord.retrieve' => $rid
138         )->gather(1);
139
140         # Now get the map of all bib records for the metarecord
141         my $recs =
142         $_storage->request(
143                 'open-ils.storage.direct.metabib.metarecord_source_map.search.metarecord.atomic',
144                 $rid
145         )->gather(1);
146
147         $logger->debug("Adding ".scalar(@$recs)." bib record to the MODS of the metarecord");
148
149         # and retrieve the lead (master) record as MODS
150         my ($master) =
151                 $self   ->method_lookup('open-ils.supercat.record.mods.retrieve')
152                         ->run($mr->master_record);
153         my $master_mods = $_parser->parse_string($master)->documentElement;
154         $master_mods->setNamespace( "http://www.loc.gov/mods/", "mods", 1 );
155
156         # ... and a MODS clone to populate, with guts removed.
157         my $mods = $_parser->parse_string($master)->documentElement;
158         $mods->setNamespace( "http://www.loc.gov/mods/", "mods", 1 );
159         ($mods) = $mods->findnodes('//mods:mods');
160         $mods->removeChildNodes;
161
162         # Add the metarecord ID as a (locally defined) info URI
163         my $recordInfo = $mods
164                 ->ownerDocument
165                 ->createElement("mods:recordInfo");
166
167         my $recordIdentifier = $mods
168                 ->ownerDocument
169                 ->createElement("mods:recordIdentifier");
170
171         $recordIdentifier->setAttribute( source => 'oils:/metabib-metarecord/' );
172
173         my $id = $mr->id;
174         $recordIdentifier->appendTextNode( $id );
175
176         $recordInfo->appendChild($recordIdentifier);
177         $mods->appendChild($recordInfo);
178
179         # Grab the title, author and ISBN for the master record and populate the metarecord
180         my ($title) = $master_mods->findnodes( './mods:titleInfo[not(@type)]' );
181         
182         if ($title) {
183                 $title->setNamespace( "http://www.loc.gov/mods/", "mods", 1 );
184                 $title = $mods->ownerDocument->importNode($title);
185                 $mods->appendChild($title);
186         }
187
188         my ($author) = $master_mods->findnodes( './mods:name[mods:role/mods:text[text()="creator"]]' );
189         if ($author) {
190                 $author->setNamespace( "http://www.loc.gov/mods/", "mods", 1 );
191                 $author = $mods->ownerDocument->importNode($author);
192                 $mods->appendChild($author);
193         }
194
195         my ($isbn) = $master_mods->findnodes( './mods:identifier[@type="isbn"]' );
196         if ($isbn) {
197                 $isbn->setNamespace( "http://www.loc.gov/mods/", "mods", 1 );
198                 $isbn = $mods->ownerDocument->importNode($isbn);
199                 $mods->appendChild($isbn);
200         }
201
202         # ... and loop over the constituent records
203         for my $map ( @$recs ) {
204
205                 # get the MODS
206                 my ($rec) =
207                         $self   ->method_lookup('open-ils.supercat.record.mods.retrieve')
208                                 ->run($map->source);
209
210                 my $part_mods = $_parser->parse_string($rec);
211                 $part_mods->documentElement->setNamespace( "http://www.loc.gov/mods/", "mods", 1 );
212                 ($part_mods) = $part_mods->findnodes('//mods:mods');
213
214                 for my $node ( ($part_mods->findnodes( './mods:subject' )) ) {
215                         $node->setNamespace( "http://www.loc.gov/mods/", "mods", 1 );
216                         $node = $mods->ownerDocument->importNode($node);
217                         $mods->appendChild( $node );
218                 }
219
220                 my $relatedItem = $mods
221                         ->ownerDocument
222                         ->createElement("mods:relatedItem");
223
224                 $relatedItem->setAttribute( type => 'constituent' );
225
226                 my $identifier = $mods
227                         ->ownerDocument
228                         ->createElement("mods:identifier");
229
230                 $identifier->setAttribute( type => 'uri' );
231
232                 my $recordInfo = $mods
233                         ->ownerDocument
234                         ->createElement("mods:recordInfo");
235
236                 my $recordIdentifier = $mods
237                         ->ownerDocument
238                         ->createElement("mods:recordIdentifier");
239
240                 $recordIdentifier->setAttribute( source => 'oils:/biblio-record_entry/' );
241
242                 my $id = $map->source;
243                 $recordIdentifier->appendTextNode( $id );
244                 $recordInfo->appendChild($recordIdentifier);
245
246                 $relatedItem->appendChild( $recordInfo );
247
248                 my ($tor) = $part_mods->findnodes( './mods:typeOfResource' );
249                 $tor->setNamespace( "http://www.loc.gov/mods/", "mods", 1 ) if ($tor);
250                 $tor = $mods->ownerDocument->importNode($tor) if ($tor);
251                 $relatedItem->appendChild($tor) if ($tor);
252
253                 if ( my ($part_isbn) = $part_mods->findnodes( './mods:identifier[@type="isbn"]' ) ) {
254                         $part_isbn->setNamespace( "http://www.loc.gov/mods/", "mods", 1 );
255                         $part_isbn = $mods->ownerDocument->importNode($part_isbn);
256                         $relatedItem->appendChild( $part_isbn );
257
258                         if (!$isbn) {
259                                 $isbn = $mods->appendChild( $part_isbn->cloneNode(1) );
260                         }
261                 }
262
263                 $mods->appendChild( $relatedItem );
264
265         }
266
267         $_storage->disconnect;
268
269         return entityize($mods->toString);
270
271 }
272 __PACKAGE__->register_method(
273         method    => 'retrieve_metarecord_mods',
274         api_name  => 'open-ils.supercat.metarecord.mods.retrieve',
275         api_level => 1,
276         argc      => 1,
277         signature =>
278                 { desc     => <<"                 DESC",
279 Returns the MODS representation of the requested metarecord
280                   DESC
281                   params   =>
282                         [
283                                 { name => 'metarecordId',
284                                   desc => 'An OpenILS metabib::metarecord id',
285                                   type => 'number' },
286                         ],
287                   'return' =>
288                         { desc => 'The metarecord in MODS',
289                           type => 'string' }
290                 }
291 );
292
293 sub oISBN {
294         my $self = shift;
295         my $client = shift;
296         my $isbn = shift;
297
298         throw OpenSRF::EX::InvalidArg ('I need an ISBN please')
299                 unless (length($isbn) >= 10);
300
301         # Create a storage session, since we'll be making muliple requests.
302         $_storage->connect;
303
304         # Find the record that has that ISBN.
305         my $bibrec = $_storage->request(
306                 'open-ils.storage.direct.metabib.full_rec.search_where.atomic',
307                 { tag => '020', subfield => 'a', value => { like => $isbn.'%'} }
308         )->gather(1);
309
310         # Go away if we don't have one.
311         return {} unless (@$bibrec);
312
313         # Find the metarecord for that bib record.
314         my $mr = $_storage->request(
315                 'open-ils.storage.direct.metabib.metarecord_source_map.search.source.atomic',
316                 $bibrec->[0]->record
317         )->gather(1);
318
319         # Find the other records for that metarecord.
320         my $records = $_storage->request(
321                 'open-ils.storage.direct.metabib.metarecord_source_map.search.metarecord.atomic',
322                 $mr->[0]->metarecord
323         )->gather(1);
324
325         # Just to be safe.  There's currently no unique constraint on sources...
326         my %unique_recs = map { ($_->source, 1) } @$records;
327         my @rec_list = sort keys %unique_recs;
328
329         # And now fetch the ISBNs for thos records.
330         my $recs = $_storage->request(
331                 'open-ils.storage.direct.metabib.full_rec.search_where.atomic',
332                 { tag => '020', subfield => 'a', record => \@rec_list }
333         )->gather(1);
334
335         # We're done with the storage server session.
336         $_storage->disconnect;
337
338         # Return the oISBN data structure.  This will be XMLized at a higher layer.
339         return
340                 { metarecord => $mr->[0]->metarecord,
341                   record_list => { map { ($_->record, $_->value) } @$recs } };
342
343 }
344 __PACKAGE__->register_method(
345         method    => 'oISBN',
346         api_name  => 'open-ils.supercat.oisbn',
347         api_level => 1,
348         argc      => 1,
349         signature =>
350                 { desc     => <<"                 DESC",
351 Returns the ISBN list for the metarecord of the requested isbn
352                   DESC
353                   params   =>
354                         [
355                                 { name => 'isbn',
356                                   desc => 'An ISBN.  Duh.',
357                                   type => 'string' },
358                         ],
359                   'return' =>
360                         { desc => 'record to isbn map',
361                           type => 'object' }
362                 }
363 );
364
365 1;