View | Details | Raw Unified | Return to bug 25905
Collapse All | Expand All

(-)a/Koha/Import/Oaipmh.pm (+208 lines)
Line 0 Link Here
1
package Koha::Import::Oaipmh;
2
3
# This file is part of Koha.
4
#
5
# Koha is free software; you can redistribute it and/or modify it
6
# under the terms of the GNU General Public License as published by
7
# the Free Software Foundation; either version 3 of the License, or
8
# (at your option) any later version.
9
#
10
# Koha is distributed in the hope that it will be useful, but
11
# WITHOUT ANY WARRANTY; without even the implied warranty of
12
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
# GNU General Public License for more details.
14
#
15
# You should have received a copy of the GNU General Public License
16
17
use Modern::Perl;
18
use XML::LibXML::Reader;
19
use MARC::Record;
20
use DateTime::Format::Strptime;
21
22
use Koha::Import::Oaipmh::Biblios;
23
use C4::Context;
24
use C4::Biblio;
25
26
my $xpc = XML::LibXML::XPathContext->new();
27
$xpc->registerNs('oai','http://www.openarchives.org/OAI/2.0/');
28
my $xpath_identifier = XML::LibXML::XPathExpression->new("oai:header/oai:identifier");
29
my $xpath_datestamp = XML::LibXML::XPathExpression->new("oai:header/oai:datestamp");
30
my $xpath_status = XML::LibXML::XPathExpression->new(q{oai:header/@status});
31
my $xpath_metadata = XML::LibXML::XPathExpression->new(q{oai:metadata/*});
32
33
my $strp = DateTime::Format::Strptime->new(
34
    pattern => '%F %T',
35
    time_zone => 'floating',
36
);
37
38
=head1 API Model
39
40
=head2 Methods
41
42
=cut
43
44
sub new {
45
    my ($class, $args) = @_;
46
    $args = {} unless defined $args;
47
    return bless ($args, $class);
48
}
49
50
#NOTE: To improve performance, you could turn off autocommit at the start of this function, then commit at the end of the function
51
sub process_metadata {
52
    my ($self, $args) = @_;
53
    my $added = 0;
54
    my $updated = 0;
55
    my $deleted = 0;
56
    my $skipped = 0;
57
58
    my $repository = $args->{repository};
59
    my $records = $args->{records};
60
    my $frameworkcode = $args->{frameworkcode} // '';
61
    if ($repository && $records) {
62
        my $reader = XML::LibXML::Reader->new( string => $records );
63
        my $each_pattern = XML::LibXML::Pattern->new('//oai:record', { 'oai' => "http://www.openarchives.org/OAI/2.0/" });
64
        while (my $rv = $reader->nextPatternMatch($each_pattern)){
65
            #NOTE: We do this so we only get the opening tag of the element.
66
            next unless $reader->nodeType == XML_READER_TYPE_ELEMENT;
67
            if ($reader->localName eq "record"){
68
                my $record_node = $reader->copyCurrentNode(1);
69
                if ($record_node){
70
                    my $document = XML::LibXML::Document->new('1.0', 'UTF-8');
71
                    $document->setDocumentElement($record_node);
72
                    my $root = $document->documentElement;
73
74
                    my $identifier = _get_node_text({
75
                        root => $root,
76
                        xpath_expression => $xpath_identifier,
77
                    });
78
                    my $datestamp = _get_node_text({
79
                        root => $root,
80
                        xpath_expression => $xpath_datestamp,
81
                    });
82
                    #Remove UTC markers from timestamp
83
                    $datestamp =~ s/T/ /;
84
                    $datestamp =~ s/Z//;
85
86
                    my $metadata;
87
                    my $metadata_node = _get_node({
88
                        root => $root,
89
                        xpath_expression => $xpath_metadata,
90
                    });
91
                    if ($metadata_node){
92
                        $metadata = $metadata_node->toString(2);
93
                    }
94
95
                    my $deleted = _get_node_text({
96
                        root => $root,
97
                        xpath_expression => $xpath_status,
98
                    });
99
100
                    if ($self->{type} && $self->{type} eq 'biblio'){
101
                        if ($identifier && $datestamp){
102
							my $record = Koha::Import::Oaipmh::Biblios->find({
103
                                repository => $repository,
104
                                identifier => $identifier,
105
							});
106
                            if ($record){
107
                                if ($metadata && ! $deleted){
108
                                    eval {
109
                                        my $incoming_dt = $strp->parse_datetime($datestamp);
110
                                        my $existing_dt = $strp->parse_datetime( $record->datestamp );
111
                                        warn $incoming_dt;
112
                                        warn $existing_dt;
113
                                        if ( $incoming_dt > $existing_dt ){
114
                                            MARC::Record::default_record_format("UNIMARC")
115
                                                if ( C4::Context->preference("marcflavour") eq "UNIMARC" );
116
                                            my $marc_record = MARC::Record::new_from_xml( $metadata, 'UTF-8' );
117
                                            C4::Biblio::ModBiblio($marc_record, $record->biblionumber, $frameworkcode);
118
                                            $record->update({
119
                                                datestamp => $datestamp,
120
                                                metadata => $metadata,
121
                                            });
122
                                            $updated++;
123
                                        } else {
124
                                            $skipped++;
125
                                        }
126
                                    };
127
                                } elsif ($deleted){
128
                                    eval {
129
                                        C4::Biblio::DelBiblio($record->biblionumber);
130
                                        #$record->update({
131
                                        #   datestamp => $datestamp,
132
                                        #   deleted => 1,
133
                                        #   metadata => ''
134
                                        #});
135
                                        $deleted++;
136
                                    };
137
                                }
138
                            } else {
139
                                #NOTE: Having metadata and being deleted should be mutually exclusive,
140
                                #but it doesn't hurt to be thorough
141
                                if ($metadata && ! $deleted){
142
                                    eval {
143
                                        MARC::Record::default_record_format("UNIMARC")
144
                                            if ( C4::Context->preference("marcflavour") eq "UNIMARC" );
145
                                        my $marc_record = MARC::Record::new_from_xml( $metadata, 'UTF-8' );
146
                                        my ($biblionumber) = C4::Biblio::AddBiblio($marc_record,$frameworkcode);
147
                                        Koha::Import::Oaipmh::Biblio->new({
148
                                            repository => $repository,
149
                                            identifier => $identifier,
150
                                            datestamp => $datestamp,
151
                                            metadata => $metadata,
152
                                            frameworkcode => $frameworkcode,
153
                                            biblionumber => $biblionumber,
154
                                        })->store;
155
                                        $added++;
156
                                    };
157
                                }
158
                            }
159
                        }
160
                    }
161
                }
162
            }
163
        }
164
    }
165
    my $results = {
166
        added => $added,
167
        updated => $updated,
168
        deleted => $deleted,
169
        skipped => $skipped,
170
    };
171
    return $results;
172
}
173
174
sub _get_node {
175
    my ($args) = @_;
176
    my $node;
177
    my $root = $args->{root};
178
    my $xpath_expression = $args->{xpath_expression};
179
    if ($root && $xpath_expression){
180
        my $node_list = $xpc->findnodes($xpath_expression,$root);
181
        if ($node_list){
182
            my $first_node = $node_list->shift;
183
            if ($first_node){
184
                $node = $first_node;
185
            }
186
        }
187
    }
188
    return $node;
189
}
190
191
sub _get_node_text {
192
    my ($args) = @_;
193
    my $text;
194
    my $root = $args->{root};
195
    my $xpath_expression = $args->{xpath_expression};
196
    if ($root && $xpath_expression){
197
        my $node = _get_node({
198
            root => $root,
199
            xpath_expression => $xpath_expression,
200
        });
201
        if ($node){
202
            $text = $node->textContent;
203
        }
204
    }
205
    return $text;
206
}
207
208
1;
(-)a/Koha/Import/Oaipmh/Biblio.pm (+40 lines)
Line 0 Link Here
1
package Koha::Import::Oaipmh::Biblio;
2
3
# This file is part of Koha.
4
#
5
# Koha is free software; you can redistribute it and/or modify it
6
# under the terms of the GNU General Public License as published by
7
# the Free Software Foundation; either version 3 of the License, or
8
# (at your option) any later version.
9
#
10
# Koha is distributed in the hope that it will be useful, but
11
# WITHOUT ANY WARRANTY; without even the implied warranty of
12
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
# GNU General Public License for more details.
14
#
15
# You should have received a copy of the GNU General Public License
16
17
use Modern::Perl;
18
19
use base qw(Koha::Object);
20
21
=head1 NAME
22
23
Koha::Import::Oaipmh::Biblio
24
25
This object represents an OAI-PMH record being imported as a biblio
26
27
=head1 API
28
29
=head2 Methods
30
31
32
=head3 type
33
34
=cut
35
36
sub _type {
37
    return 'ImportOaipmhBiblio';
38
}
39
40
1;
(-)a/Koha/Import/Oaipmh/Biblios.pm (+47 lines)
Line 0 Link Here
1
package Koha::Import::Oaipmh::Biblios;
2
3
# This file is part of Koha.
4
#
5
# Koha is free software; you can redistribute it and/or modify it
6
# under the terms of the GNU General Public License as published by
7
# the Free Software Foundation; either version 3 of the License, or
8
# (at your option) any later version.
9
#
10
# Koha is distributed in the hope that it will be useful, but
11
# WITHOUT ANY WARRANTY; without even the implied warranty of
12
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
# GNU General Public License for more details.
14
#
15
# You should have received a copy of the GNU General Public License
16
17
use Modern::Perl;
18
19
use Koha::Import::Oaipmh::Biblio;
20
21
use base qw(Koha::Objects);
22
23
=head1 NAME
24
25
Koha::Import::Oaipmh::Biblios
26
27
This object represents a collection of OAI-PMH records being imported as biblios
28
29
=head1 API
30
31
=head2 Methods
32
33
34
=head3 type
35
36
=cut
37
38
sub _type {
39
    return 'ImportOaipmhBiblio';
40
}
41
42
43
sub object_class {
44
    return 'Koha::Import::Oaipmh::Biblio'
45
}
46
47
1;
(-)a/Koha/REST/V1/Import/Oaipmh/Biblios.pm (+68 lines)
Line 0 Link Here
1
package Koha::REST::V1::Import::Oaipmh::Biblios;
2
3
# This file is part of Koha.
4
#
5
# Koha is free software; you can redistribute it and/or modify it
6
# under the terms of the GNU General Public License as published by
7
# the Free Software Foundation; either version 3 of the License, or
8
# (at your option) any later version.
9
#
10
# Koha is distributed in the hope that it will be useful, but
11
# WITHOUT ANY WARRANTY; without even the implied warranty of
12
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
# GNU General Public License for more details.
14
#
15
# You should have received a copy of the GNU General Public License
16
17
use Modern::Perl;
18
19
use Mojo::Base 'Mojolicious::Controller';
20
21
use Koha::Import::Oaipmh;
22
23
#use Try::Tiny;
24
25
=head1 API
26
27
=head2 Class Methods
28
29
=head3 Method that imports bibliographic records from OAI-PMH
30
31
=cut
32
33
sub import_biblios {
34
    #NOTE: Validation will only work if body is marked as optional, as it's a non-JSON body
35
    my $c = shift->openapi->valid_input or return;
36
37
    my $repository = $c->validation->param('x-koha-oaipmh-repository');
38
    #TODO: validate the repository (ie check that it is a URL)
39
40
    my $frameworkcode = $c->validation->param('x-koha-frameworkcode') // '';
41
    #TODO: validate the frameworkcode (ie check that it is a frameworkcode that exists)
42
43
    #NOTE: Mojolicious::Plugin::OpenAPI expects a JSON body, so we can't use the validation
44
    #my $records = $c->validation->param('body');
45
    my $records = $c->req->body;
46
    if ($records) {
47
        my $importer = Koha::Import::Oaipmh->new({
48
            type => 'biblio',
49
        });
50
        #NOTE: Don't use these variables as API output, since one day you want the API to just stage 
51
        #the records and not process them synchronously. Returning no "useful" output keeps the API
52
        #externally consistent.
53
        my $results = $importer->process_metadata({
54
            repository => $repository,
55
            records => $records,
56
            frameworkcode => $frameworkcode,
57
        });
58
        use Data::Dumper;
59
        warn Dumper($results);
60
    } else {
61
        return $c->render( status => 400, openapi => { error => "Records not found." } );
62
    }
63
    return $c->render( status => 200, openapi => undef );
64
}
65
66
1;
67
68
(-)a/api/v1/swagger/paths.json (+3 lines)
Lines 130-134 Link Here
130
  },
130
  },
131
  "/return_claims/{claim_id}": {
131
  "/return_claims/{claim_id}": {
132
    "$ref": "paths/return_claims.json#/~1return_claims~1{claim_id}"
132
    "$ref": "paths/return_claims.json#/~1return_claims~1{claim_id}"
133
  },
134
  "/import/oaipmh/biblios": {
135
    "$ref": "paths/import_oaipmh_biblios.json#/~1import~1oaipmh~1biblios"
133
  }
136
  }
134
}
137
}
(-)a/api/v1/swagger/paths/import_oaipmh_biblios.json (-1 / +63 lines)
Line 0 Link Here
0
- 
1
{
2
  "/import/oaipmh/biblios": {
3
    "post": {
4
      "x-mojo-to": "Import::Oaipmh::Biblios#import_biblios",
5
      "operationId": "import_biblios",
6
      "tags": ["biblios"],
7
      "parameters": [
8
        {
9
          "name": "body",
10
          "in": "body",
11
          "description": "OAI-PMH XML bibliographic records",
12
          "required": false,
13
          "schema": {
14
            "type": "string"
15
          }
16
        },
17
        {
18
          "name": "x-koha-oaipmh-repository",
19
          "in": "header",
20
          "required": true,
21
          "description": "OAI-PMH repository sent as a request header",
22
          "type": "string"
23
        },
24
        {
25
          "name": "x-koha-frameworkcode",
26
          "in": "header",
27
          "required": false,
28
          "description": "MARC bibliographic framework code to use for importing bib records",
29
          "type": "string"
30
        }
31
      ],
32
      "consumes": [
33
        "text/xml"
34
      ],
35
      "produces": [
36
        "application/json"
37
      ],
38
      "responses": {
39
        "200": {
40
          "description": "Records accepted"
41
        },
42
		"400": {
43
          "description": "An error occured",
44
          "schema": {
45
              "type": "object",
46
                "properties": {
47
                  "error": {
48
                    "description": "An explanation for the error",
49
                    "type": "string"
50
                  }
51
                }
52
          }
53
        }
54
      },
55
      "x-koha-authorization": {
56
        "permissions": {
57
          "editcatalogue": "edit_catalogue"
58
        }
59
      }
60
    }
61
  }
62
}
63

Return to bug 25905