Bug 15108: OAI-PMH provider improvements
[srvgit] / Koha / OAI / Server / Repository.pm
1 # Copyright Tamil s.a.r.l. 2008-2015
2 # Copyright Biblibre 2008-2015
3 # Copyright The National Library of Finland, University of Helsinki 2016
4 #
5 # This file is part of Koha.
6 #
7 # Koha is free software; you can redistribute it and/or modify it
8 # under the terms of the GNU General Public License as published by
9 # the Free Software Foundation; either version 3 of the License, or
10 # (at your option) any later version.
11 #
12 # Koha is distributed in the hope that it will be useful, but
13 # WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU General Public License for more details.
16 #
17 # You should have received a copy of the GNU General Public License
18 # along with Koha; if not, see <http://www.gnu.org/licenses>.
19
20 package Koha::OAI::Server::Repository;
21
22 use Modern::Perl;
23 use HTTP::OAI;
24 use HTTP::OAI::Repository qw/:validate/;
25
26 use base ("HTTP::OAI::Repository");
27
28 use Koha::OAI::Server::Identify;
29 use Koha::OAI::Server::ListSets;
30 use Koha::OAI::Server::ListMetadataFormats;
31 use Koha::OAI::Server::GetRecord;
32 use Koha::OAI::Server::ListRecords;
33 use Koha::OAI::Server::ListIdentifiers;
34 use XML::SAX::Writer;
35 use XML::LibXML;
36 use XML::LibXSLT;
37 use YAML::Syck qw( LoadFile );
38 use CGI qw/:standard -oldstyle_urls/;
39 use C4::Context;
40 use C4::Biblio;
41
42
43 =head1 NAME
44
45 Koha::OAI::Server::Repository - Handles OAI-PMH requests for a Koha database.
46
47 =head1 SYNOPSIS
48
49   use Koha::OAI::Server::Repository;
50
51   my $repository = Koha::OAI::Server::Repository->new();
52
53 =head1 DESCRIPTION
54
55 This object extend HTTP::OAI::Repository object.
56 It accepts OAI-PMH HTTP requests and returns result.
57
58 This OAI-PMH server can operate in a simple mode and extended one.
59
60 In simple mode, repository configuration comes entirely from Koha system
61 preferences (OAI-PMH:archiveID and OAI-PMH:MaxCount) and the server returns
62 records in marcxml or dublin core format. Dublin core records are created from
63 koha marcxml records transformed with XSLT. Used XSL file is located in koha-
64 tmpl/intranet-tmpl/prog/en/xslt directory and chosen based on marcflavour,
65 respecively MARC21slim2OAIDC.xsl for MARC21 and  MARC21slim2OAIDC.xsl for
66 UNIMARC.
67
68 In extended mode, it's possible to parameter other format than marcxml or
69 Dublin Core. A new syspref OAI-PMH:ConfFile specify a YAML configuration file
70 which list available metadata formats and XSL file used to create them from
71 marcxml records. If this syspref isn't set, Koha OAI server works in simple
72 mode. A configuration file koha-oai.conf can look like that:
73
74   ---
75   format:
76     vs:
77       metadataPrefix: vs
78       metadataNamespace: http://veryspecial.tamil.fr/vs/format-pivot/1.1/vs
79       schema: http://veryspecial.tamil.fr/vs/format-pivot/1.1/vs.xsd
80       xsl_file: /usr/local/koha/xslt/vs.xsl
81     marc21:
82       metadataPrefix: marc21
83       metadataNamespace: http://www.loc.gov/MARC21/slim http://www.loc.gov/standards/marcxml/schema/MARC21slim
84       schema: http://www.loc.gov/MARC21/slim http://www.loc.gov/standards/marcxml/schema/MARC21slim.xsd
85       include_items: 1
86     marcxml:
87       metadataPrefix: marxml
88       metadataNamespace: http://www.loc.gov/MARC21/slim http://www.loc.gov/standards/marcxml/schema/MARC21slim
89       schema: http://www.loc.gov/MARC21/slim http://www.loc.gov/standards/marcxml/schema/MARC21slim.xsd
90       include_items: 1
91     oai_dc:
92       metadataPrefix: oai_dc
93       metadataNamespace: http://www.openarchives.org/OAI/2.0/oai_dc/
94       schema: http://www.openarchives.org/OAI/2.0/oai_dc.xsd
95       xsl_file: /usr/local/koha/koha-tmpl/intranet-tmpl/xslt/UNIMARCslim2OAIDC.xsl
96
97 Note the 'include_items' parameter which is the only mean to return item-level info.
98
99 =cut
100
101
102 sub new {
103     my ($class, %args) = @_;
104     my $self = $class->SUPER::new(%args);
105
106     $self->{ koha_identifier      } = C4::Context->preference("OAI-PMH:archiveID");
107     $self->{ koha_max_count       } = C4::Context->preference("OAI-PMH:MaxCount");
108     $self->{ koha_metadata_format } = ['oai_dc', 'marc21', 'marcxml'];
109     $self->{ koha_stylesheet      } = { }; # Build when needed
110
111     # Load configuration file if defined in OAI-PMH:ConfFile syspref
112     if ( my $file = C4::Context->preference("OAI-PMH:ConfFile") ) {
113         $self->{ conf } = LoadFile( $file );
114         my @formats = keys %{ $self->{conf}->{format} };
115         $self->{ koha_metadata_format } =  \@formats;
116     }
117
118     # OAI-PMH handles dates in UTC, so do that on the database level to avoid need for
119     # any conversions
120     C4::Context->dbh->prepare("SET time_zone='+00:00'")->execute();
121
122     # Check for grammatical errors in the request
123     my @errs = validate_request( CGI::Vars() );
124
125     # Is metadataPrefix supported by the repository?
126     my $mdp = param('metadataPrefix') || '';
127     if ( $mdp && !grep { $_ eq $mdp } @{$self->{ koha_metadata_format }} ) {
128         push @errs, new HTTP::OAI::Error(
129             code    => 'cannotDisseminateFormat',
130             message => "Dissemination as '$mdp' is not supported",
131         );
132     }
133
134     my $response;
135     if ( @errs ) {
136         $response = HTTP::OAI::Response->new(
137             requestURL  => self_url(),
138             errors      => \@errs,
139         );
140     }
141     else {
142         my %attr = CGI::Vars();
143         my $verb = delete $attr{verb};
144         my $class = "Koha::OAI::Server::$verb";
145         $response = $class->new($self, %attr);
146     }
147
148     $response->set_handler( XML::SAX::Writer->new( Output => *STDOUT ) );
149     $response->xslt( "/opac-tmpl/xslt/OAI.xslt" );
150     $response->generate;
151
152     bless $self, $class;
153     return $self;
154 }
155
156
157 sub get_biblio_marcxml {
158     my ($self, $biblionumber, $format) = @_;
159     my $with_items = 0;
160     if ( my $conf = $self->{conf} ) {
161         $with_items = $conf->{format}->{$format}->{include_items};
162     }
163     my $record = GetMarcBiblio($biblionumber, $with_items, 1);
164     $record ? $record->as_xml_record() : undef;
165 }
166
167
168 sub stylesheet {
169     my ( $self, $format ) = @_;
170
171     my $stylesheet = $self->{ koha_stylesheet }->{ $format };
172     unless ( $stylesheet ) {
173         my $xsl_file = $self->{ conf }
174                        ? $self->{ conf }->{ format }->{ $format }->{ xsl_file }
175                        : ( C4::Context->config('intrahtdocs') .
176                          '/prog/en/xslt/' .
177                          C4::Context->preference('marcflavour') .
178                          'slim2OAIDC.xsl' );
179         $xsl_file || die( "No stylesheet found for $format" );
180         my $parser = XML::LibXML->new();
181         my $xslt = XML::LibXSLT->new();
182         my $style_doc = $parser->parse_file( $xsl_file );
183         $stylesheet = $xslt->parse_stylesheet( $style_doc );
184         $self->{ koha_stylesheet }->{ $format } = $stylesheet;
185     }
186
187     return $stylesheet;
188 }
189
190
191 sub items_included {
192     my ( $self, $format ) = @_;
193
194     if ( my $conf = $self->{ conf } ) {
195         return $conf->{ format }->{ $format }->{ include_items };
196     }
197     return 0;
198 }
199
200 1;