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

(-)a/Koha/Z3950Responder.pm (-15 / +17 lines)
Lines 24-32 use Modern::Perl; Link Here
24
use C4::Biblio qw( GetMarcFromKohaField );
24
use C4::Biblio qw( GetMarcFromKohaField );
25
use C4::Koha qw( GetAuthorisedValues );
25
use C4::Koha qw( GetAuthorisedValues );
26
26
27
use Koha;
28
use Koha::Z3950Responder::Session;
29
30
use Net::Z3950::SimpleServer;
27
use Net::Z3950::SimpleServer;
31
28
32
sub new {
29
sub new {
Lines 66-75 sub new { Link Here
66
        unshift @{ $self->{yaz_options} }, '-v', 'none,fatal';
63
        unshift @{ $self->{yaz_options} }, '-v', 'none,fatal';
67
    }
64
    }
68
65
66
    # Set main config for SRU support
67
    unshift @{ $self->{yaz_options} }, '-f', $self->{config_dir} . 'config.xml' if $self->{config_dir};
68
69
    $self->{server} = Net::Z3950::SimpleServer->new(
69
    $self->{server} = Net::Z3950::SimpleServer->new(
70
        INIT => sub { $self->init_handler(@_) },
70
        INIT => sub { $self->init_handler(@_) },
71
        SEARCH => sub { $self->search_handler(@_) },
71
        SEARCH => sub { $self->search_handler(@_) },
72
        PRESENT => sub { $self->present_handler(@_) },
73
        FETCH => sub { $self->fetch_handler(@_) },
72
        FETCH => sub { $self->fetch_handler(@_) },
74
        CLOSE => sub { $self->close_handler(@_) },
73
        CLOSE => sub { $self->close_handler(@_) },
75
    );
74
    );
Lines 92-101 sub init_handler { Link Here
92
    my ( $self, $args ) = @_;
91
    my ( $self, $args ) = @_;
93
92
94
    # This holds all of the per-connection state.
93
    # This holds all of the per-connection state.
95
    my $session = Koha::Z3950Responder::Session->new({
94
    my $session;
96
        server => $self,
95
    if (C4::Context->preference('SearchEngine') eq 'Zebra') {
97
        peer => $args->{PEER_NAME},
96
        use Koha::Z3950Responder::ZebraSession;
98
    });
97
        $session = Koha::Z3950Responder::ZebraSession->new({
98
            server => $self,
99
            peer => $args->{PEER_NAME},
100
        });
101
    } else {
102
        use Koha::Z3950Responder::GenericSession;
103
        $session = Koha::Z3950Responder::GenericSession->new({
104
            server => $self,
105
            peer => $args->{PEER_NAME}
106
        });
107
    }
99
108
100
    $args->{HANDLE} = $session;
109
    $args->{HANDLE} = $session;
101
110
Lines 110-122 sub search_handler { Link Here
110
    $args->{HANDLE}->search_handler($args);
119
    $args->{HANDLE}->search_handler($args);
111
}
120
}
112
121
113
sub present_handler {
114
    # Called when a set of records is requested.
115
    my ( $self, $args ) = @_;
116
117
    $args->{HANDLE}->present_handler($args);
118
}
119
120
sub fetch_handler {
122
sub fetch_handler {
121
    # Called when a given record is requested.
123
    # Called when a given record is requested.
122
    my ( $self, $args ) = @_;
124
    my ( $self, $args ) = @_;
(-)a/Koha/Z3950Responder/GenericSession.pm (+90 lines)
Line 0 Link Here
1
#!/usr/bin/perl
2
3
package Koha::Z3950Responder::GenericSession;
4
5
# Copyright The National Library of Finland 2018
6
#
7
# This file is part of Koha.
8
#
9
# Koha is free software; you can redistribute it and/or modify it under the
10
# terms of the GNU General Public License as published by the Free Software
11
# Foundation; either version 3 of the License, or (at your option) any later
12
# version.
13
#
14
# Koha is distributed in the hope that it will be useful, but WITHOUT ANY
15
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
16
# A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
17
#
18
# You should have received a copy of the GNU General Public License along
19
# with Koha; if not, write to the Free Software Foundation, Inc.,
20
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21
22
use Modern::Perl;
23
24
use base qw( Koha::Z3950Responder::Session );
25
26
use Koha::Logger;
27
use Koha::SearchEngine::Search;
28
use Koha::SearchEngine::QueryBuilder;
29
use Koha::Z3950Responder::RPN;
30
31
sub _start_search {
32
    my ( $self, $args, $num_to_prefetch ) = @_;
33
34
    if (!defined $self->{'attribute_mappings'}) {
35
        require YAML::Any;
36
        $self->{'attribute_mappings'} = YAML::Any::LoadFile($self->{server}->{config_dir} . 'attribute_mappings.yaml');
37
    }
38
39
    my $database = $args->{DATABASES}->[0];
40
    my $builder = Koha::SearchEngine::QueryBuilder->new({ index => $database });
41
    my $searcher = Koha::SearchEngine::Search->new({ index => $database });
42
43
    my $built_query;
44
    my $query = $args->{RPN}->{'query'}->to_koha($self->{'attribute_mappings'}->{$database});
45
    $self->_log_debug("    parsed search: $query");
46
    my @operands = $query;
47
    (undef, $built_query) = $builder->build_query_compat( undef, \@operands, undef, undef, undef, 0);
48
49
    my ($error, $marcresults, $hits ) = $searcher->simple_search_compat($built_query, 0, $num_to_prefetch);
50
    if (defined $error) {
51
        $self->_set_error($args, $self->ERR_SEARCH_FAILED, 'Search failed');
52
        return;
53
    }
54
55
    my $resultset = {
56
        query => $built_query,
57
        database => $database,
58
        cached_offset => 0,
59
        cached_results => $marcresults,
60
        hits => $hits
61
    };
62
63
    return ($resultset, $hits);
64
}
65
66
sub _fetch_record {
67
    my ( $self, $resultset, $args, $index, $num_to_prefetch ) = @_;
68
69
    # Fetch more records if necessary
70
    my $offset = $args->{OFFSET} - 1;
71
    if ($offset >= $resultset->{cached_offset} + $num_to_prefetch) {
72
        $self->_log_debug("    fetch uncached, fetching $num_to_prefetch records starting at $offset");
73
        my $searcher = Koha::SearchEngine::Search->new({ index => $resultset->{'database'} });
74
        my ($error, $marcresults, $num_hits ) = $searcher->simple_search_compat($resultset->{'query'}, $offset, $num_to_prefetch);
75
        if (defined $error) {
76
            $self->_set_error($args, $self->ERR_TEMPORARY_ERROR, 'Fetch failed');
77
            return;
78
        }
79
80
        $resultset->{cached_offset} = $offset;
81
        $resultset->{cached_results} = $marcresults;
82
    }
83
    return $resultset->{cached_results}[$offset - $resultset->{cached_offset}];
84
}
85
86
sub close_handler {
87
    my ( $self, $args ) = @_;
88
}
89
90
1;
(-)a/Koha/Z3950Responder/RPN.pm (+103 lines)
Line 0 Link Here
1
#!/usr/bin/perl
2
3
# Copyright The National Library of Finland 2018
4
#
5
# This file is part of Koha.
6
#
7
# Koha is free software; you can redistribute it and/or modify it under thes
8
# terms of the GNU General Public License as published by the Free Software
9
# Foundation; either version 3 of the License, or (at your option) any later
10
# version.
11
#
12
# Koha is distributed in the hope that it will be useful, but WITHOUT ANY
13
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
14
# A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
15
#
16
# You should have received a copy of the GNU General Public License along
17
# with Koha; if not, write to the Free Software Foundation, Inc.,
18
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19
20
use Modern::Perl;
21
22
use C4::Context;
23
24
package Net::Z3950::RPN::Term;
25
sub to_koha {
26
    my ($self, $mappings) = @_;
27
28
    my $attrs = $self->{'attributes'};
29
    my $fields = $mappings->{use}{default} // '_all';
30
    my $split = 0;
31
    my $quote = '';
32
    my $prefix = '';
33
    my $suffix = '';
34
    my $term = $self->{'term'};
35
36
    if ($attrs) {
37
        foreach my $attr (@$attrs) {
38
            if ($attr->{'attributeType'} == 1) { # use
39
                my $use = $attr->{'attributeValue'};
40
                $fields = $mappings->{use}{$use} if defined $mappings->{use}{$use};
41
            } elsif ($attr->{'attributeType'} == 4) { # structure
42
                $split = 1 if ($attr->{'attributeValue'} == 2);
43
                $quote = '"' if ($attr->{'attributeValue'} == 1);
44
            } elsif ($attr->{'attributeType'} == 5) { # truncation
45
                my $truncation = $attr->{'attributeValue'};
46
                $prefix = '*' if ($truncation == 2 || $truncation == 3);
47
                $suffix = '*' if ($truncation == 1 || $truncation == 3);
48
            }
49
        }
50
    }
51
52
    $fields = [$fields] unless ref($fields) eq 'ARRAY';
53
54
    if ($split) {
55
        my @terms;
56
        foreach my $word (split(/\s/, $term)) {
57
            $word =~ s/^[\,\.;:\\\/\"\'\-\=]+//g;
58
            $word =~ s/[\,\.;:\\\/\"\'\-\=]+$//g;
59
            next if (!$word);
60
            my @words;
61
            foreach my $field (@{$fields}) {
62
                push(@words, "$field:($prefix$word$suffix)");
63
            }
64
            push (@terms, join(' OR ', @words));
65
        }
66
        return '(' . join(' AND ', @terms) . ')';
67
    }
68
69
    my @terms;
70
    foreach my $field (@{$fields}) {
71
        push(@terms, "$field:($prefix$term$suffix)");
72
    }
73
    return '(' . join(' OR ', @terms) . ')';
74
}
75
76
package Net::Z3950::RPN::And;
77
sub to_koha
78
{
79
    my ($self, $mappings) = @_;
80
81
    return '(' . $self->[0]->to_koha($mappings) . ' AND ' .
82
                 $self->[1]->to_koha($mappings) . ')';
83
}
84
85
package Net::Z3950::RPN::Or;
86
sub to_koha
87
{
88
    my ($self, $mappings) = @_;
89
90
    return '(' . $self->[0]->to_koha($mappings) . ' OR ' .
91
                 $self->[1]->to_koha($mappings) . ')';
92
}
93
94
package Net::Z3950::RPN::AndNot;
95
sub to_koha
96
{
97
    my ($self, $mappings) = @_;
98
99
    return '(' . $self->[0]->to_koha($mappings) . ' NOT ' .
100
                 $self->[1]->to_koha($mappings) . ')';
101
}
102
103
1;
(-)a/Koha/Z3950Responder/Session.pm (-120 / +16 lines)
Lines 28-35 use C4::Reserves qw( GetReserveStatus ); Link Here
28
use C4::Search qw();
28
use C4::Search qw();
29
use Koha::Logger;
29
use Koha::Logger;
30
30
31
use ZOOM;
32
33
use constant {
31
use constant {
34
    UNIMARC_OID => '1.2.840.10003.5.1',
32
    UNIMARC_OID => '1.2.840.10003.5.1',
35
    USMARC_OID => '1.2.840.10003.5.10',
33
    USMARC_OID => '1.2.840.10003.5.10',
Lines 41-47 use constant { Link Here
41
    ERR_PRESENT_OUT_OF_RANGE => 13,
39
    ERR_PRESENT_OUT_OF_RANGE => 13,
42
    ERR_RECORD_TOO_LARGE => 16,
40
    ERR_RECORD_TOO_LARGE => 16,
43
    ERR_NO_SUCH_RESULTSET => 30,
41
    ERR_NO_SUCH_RESULTSET => 30,
44
    ERR_SYNTAX_UNSUPPORTED => 230,
42
    ERR_SEARCH_FAILED => 125,
43
    ERR_SYNTAX_UNSUPPORTED => 239,
45
    ERR_DB_DOES_NOT_EXIST => 235,
44
    ERR_DB_DOES_NOT_EXIST => 235,
46
};
45
};
47
46
Lines 86-138 sub _set_error { Link Here
86
    $self->_log_error("    returning error $code: $msg");
85
    $self->_log_error("    returning error $code: $msg");
87
}
86
}
88
87
89
sub _set_error_from_zoom {
90
    my ( $self, $args, $exception ) = @_;
91
92
    $self->_set_error( $args, ERR_TEMPORARY_ERROR, 'Cannot connect to upstream server' );
93
    $self->_log_error(
94
        "Zebra upstream error: " .
95
        $exception->message() . " (" .
96
        $exception->code() . ") " .
97
        ( $exception->addinfo() // '' ) . " " .
98
        $exception->diagset()
99
    );
100
}
101
102
# This code originally went through C4::Search::getRecords, but had to use so many escape hatches
103
# that it was easier to directly connect to Zebra.
104
sub _start_search {
105
    my ( $self, $args, $in_retry ) = @_;
106
107
    my $database = $args->{DATABASES}->[0];
108
    my ( $connection, $results );
109
110
    eval {
111
        $connection = C4::Context->Zconn(
112
            # We're depending on the caller to have done some validation.
113
            $database eq 'biblios' ? 'biblioserver' : 'authorityserver',
114
            0 # No, no async, doesn't really help much for single-server searching
115
        );
116
117
        $results = $connection->search_pqf( $args->{QUERY} );
118
119
        $self->_log_debug('    retry successful') if ($in_retry);
120
    };
121
    if ($@) {
122
        die $@ if ( ref($@) ne 'ZOOM::Exception' );
123
124
        if ( $@->diagset() eq 'ZOOM' && $@->code() == 10004 && !$in_retry ) {
125
            $self->_log_debug('    upstream server lost connection, retrying');
126
            return $self->_start_search( $args, 1 );
127
        }
128
129
        $self->_set_error_from_zoom( $args, $@ );
130
        $connection = undef;
131
    }
132
133
    return ( $connection, $results, $results ? $results->size() : -1 );
134
}
135
136
sub _check_fetch {
88
sub _check_fetch {
137
    my ( $self, $resultset, $args, $offset, $num_records ) = @_;
89
    my ( $self, $resultset, $args, $offset, $num_records ) = @_;
138
90
Lines 149-236 sub _check_fetch { Link Here
149
    return 1;
101
    return 1;
150
}
102
}
151
103
152
sub _fetch_record {
153
    my ( $self, $resultset, $args, $index, $num_to_prefetch ) = @_;
154
155
    my $record;
156
157
    eval {
158
        if ( !$resultset->{results}->record_immediate( $index ) ) {
159
            my $start = $num_to_prefetch ? int( $index / $num_to_prefetch ) * $num_to_prefetch : $index;
160
161
            if ( $start + $num_to_prefetch >= $resultset->{results}->size() ) {
162
                $num_to_prefetch = $resultset->{results}->size() - $start;
163
            }
164
165
            $self->_log_debug("    fetch uncached, fetching $num_to_prefetch records starting at $start");
166
167
            $resultset->{results}->records( $start, $num_to_prefetch, 0 );
168
        }
169
170
        $record = $resultset->{results}->record_immediate( $index )->raw();
171
    };
172
    if ($@) {
173
        die $@ if ( ref($@) ne 'ZOOM::Exception' );
174
        $self->_set_error_from_zoom( $args, $@ );
175
        return;
176
    } else {
177
        return $record;
178
    }
179
}
180
181
sub search_handler {
104
sub search_handler {
182
    # Called when search is first sent.
105
    # Called when search is first sent.
183
    my ( $self, $args ) = @_;
106
    my ( $self, $args ) = @_;
184
107
185
    my $database = $args->{DATABASES}->[0];
108
    my $database = $args->{DATABASES}->[0];
186
109
187
    if ( $database !~ /^(biblios|authorities)$/ ) {
110
    if ( $database ne $Koha::SearchEngine::BIBLIOS_INDEX && $database ne $Koha::SearchEngine::AUTHORITIES_INDEX ) {
188
        $self->_set_error( $args, ERR_DB_DOES_NOT_EXIST, 'No such database' );
111
        $self->_set_error( $args, $self->ERR_DB_DOES_NOT_EXIST, 'No such database' );
189
        return;
112
        return;
190
    }
113
    }
191
114
192
    my $query = $args->{QUERY};
115
    my $query = $args->{QUERY};
193
    $self->_log_info("received search for '$query', (RS $args->{SETNAME})");
116
    $self->_log_info("received search for '$query', (RS $args->{SETNAME})");
194
117
195
    my ( $connection, $results, $num_hits ) = $self->_start_search( $args );
118
    my ($resultset, $hits) = $self->_start_search( $args, $self->{server}->{num_to_prefetch} );
196
    return unless $connection;
119
    return unless $resultset;
197
198
    $args->{HITS} = $num_hits;
199
    my $resultset = $self->{resultsets}->{ $args->{SETNAME} } = {
200
        database => $database,
201
        connection => $connection,
202
        results => $results,
203
        query => $args->{QUERY},
204
        hits => $args->{HITS},
205
    };
206
}
207
208
sub present_handler {
209
    # Called when a set of records is requested.
210
    my ( $self, $args ) = @_;
211
212
    $self->_log_debug("received present for $args->{SETNAME}, $args->{START}+$args->{NUMBER}");
213
214
    my $resultset = $self->{resultsets}->{ $args->{SETNAME} };
215
    # The offset comes across 1-indexed.
216
    my $offset = $args->{START} - 1;
217
218
    return unless $self->_check_fetch( $resultset, $args, $offset, $args->{NUMBER} );
219
120
121
    $args->{HITS} = $hits;
122
    $self->{resultsets}->{ $args->{SETNAME} } = $resultset;
220
}
123
}
221
124
222
sub fetch_handler {
125
sub fetch_handler {
223
    # Called when a given record is requested.
126
    # Called when a given record is requested.
224
    my ( $self, $args ) = @_;
127
    my ( $self, $args ) = @_;
225
    my $session = $args->{HANDLE};
128
129
    $self->_log_debug("received fetch for RS $args->{SETNAME}, record $args->{OFFSET}");
130
226
    my $server = $self->{server};
131
    my $server = $self->{server};
227
132
228
    $self->_log_debug("received fetch for $args->{SETNAME}, record $args->{OFFSET}");
229
    my $form_oid = $args->{REQ_FORM} // '';
133
    my $form_oid = $args->{REQ_FORM} // '';
230
    my $composition = $args->{COMP} // '';
134
    my $composition = $args->{COMP} // '';
231
    $self->_log_debug("    form OID $form_oid, composition $composition");
135
    $self->_log_debug("    form OID '$form_oid', composition '$composition'");
232
136
233
    my $resultset = $session->{resultsets}->{ $args->{SETNAME} };
137
    my $resultset = $self->{resultsets}->{ $args->{SETNAME} };
234
    # The offset comes across 1-indexed.
138
    # The offset comes across 1-indexed.
235
    my $offset = $args->{OFFSET} - 1;
139
    my $offset = $args->{OFFSET} - 1;
236
140
Lines 254-265 sub fetch_handler { Link Here
254
        }
158
        }
255
    }
159
    }
256
160
257
    if ( $form_oid eq MARCXML_OID && $composition eq 'marcxml' ) {
161
    if ( $form_oid eq $self->MARCXML_OID && $composition eq 'marcxml' ) {
258
        $args->{RECORD} = $record->as_xml_record();
162
        $args->{RECORD} = $record->as_xml_record();
259
    } elsif ( ( $form_oid eq USMARC_OID || $form_oid eq UNIMARC_OID ) && ( !$composition || $composition eq 'F' ) ) {
163
    } elsif ( ( $form_oid eq $self->USMARC_OID || $form_oid eq $self->UNIMARC_OID ) && ( !$composition || $composition eq 'F' ) ) {
260
        $args->{RECORD} = $record->as_usmarc();
164
        $args->{RECORD} = $record->as_usmarc();
261
    } else {
165
    } else {
262
        $self->_set_error( $args, ERR_SYNTAX_UNSUPPORTED, "Unsupported syntax/composition $form_oid/$composition" );
166
        $self->_set_error( $args, $self->ERR_SYNTAX_UNSUPPORTED, "Unsupported syntax/composition $form_oid/$composition" );
263
        return;
167
        return;
264
    }
168
    }
265
}
169
}
Lines 318-329 sub add_item_status { Link Here
318
    }
222
    }
319
}
223
}
320
224
321
sub close_handler {
322
    my ( $self, $args ) = @_;
323
324
    foreach my $resultset ( values %{ $self->{resultsets} } ) {
325
        $resultset->{results}->destroy();
326
    }
327
}
328
329
1;
225
1;
(-)a/Koha/Z3950Responder/ZebraSession.pm (+123 lines)
Line 0 Link Here
1
#!/usr/bin/perl
2
3
package Koha::Z3950Responder::ZebraSession;
4
5
# Copyright ByWater Solutions 2016
6
#
7
# This file is part of Koha.
8
#
9
# Koha is free software; you can redistribute it and/or modify it under the
10
# terms of the GNU General Public License as published by the Free Software
11
# Foundation; either version 3 of the License, or (at your option) any later
12
# version.
13
#
14
# Koha is distributed in the hope that it will be useful, but WITHOUT ANY
15
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
16
# A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
17
#
18
# You should have received a copy of the GNU General Public License along
19
# with Koha; if not, write to the Free Software Foundation, Inc.,
20
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21
22
use Modern::Perl;
23
24
use base qw( Koha::Z3950Responder::Session );
25
26
use Koha::Logger;
27
28
use ZOOM;
29
30
sub _set_error_from_zoom {
31
    my ( $self, $args, $exception ) = @_;
32
33
    $self->_set_error( $args, $self->ERR_TEMPORARY_ERROR, 'Cannot connect to upstream server' );
34
    $self->_log_error(
35
        "Zebra upstream error: " .
36
        $exception->message() . " (" .
37
        $exception->code() . ") " .
38
        ( $exception->addinfo() // '' ) . " " .
39
        $exception->diagset()
40
    );
41
}
42
43
# This code originally went through C4::Search::getRecords, but had to use so many escape hatches
44
# that it was easier to directly connect to Zebra.
45
sub _start_search {
46
    my ( $self, $args, $num_to_prefetch, $in_retry ) = @_;
47
48
    my $database = $args->{DATABASES}->[0];
49
    my ( $connection, $results );
50
51
    eval {
52
        $connection = C4::Context->Zconn(
53
            # We're depending on the caller to have done some validation.
54
            $database eq 'biblios' ? 'biblioserver' : 'authorityserver',
55
            0 # No, no async, doesn't really help much for single-server searching
56
        );
57
58
        $results = $connection->search_pqf( $args->{QUERY} );
59
60
        $self->_log_debug('    retry successful') if ($in_retry);
61
    };
62
    if ($@) {
63
        die $@ if ( ref($@) ne 'ZOOM::Exception' );
64
65
        if ( $@->diagset() eq 'ZOOM' && $@->code() == 10004 && !$in_retry ) {
66
            $self->_log_debug('    upstream server lost connection, retrying');
67
            return $self->_start_search( $args, $num_to_prefetch, 1 );
68
        }
69
70
        $self->_set_error_from_zoom( $args, $@ );
71
        $connection = undef;
72
    }
73
74
    my $hits = $results ? $results->size() : -1;
75
    my $resultset = {
76
        database => $database,
77
        connection => $connection,
78
        results => $results,
79
        query => $args->{QUERY},
80
        hits => $hits
81
    };
82
83
    return ( $resultset, $hits );
84
}
85
86
sub _fetch_record {
87
    my ( $self, $resultset, $args, $index, $num_to_prefetch ) = @_;
88
89
    my $record;
90
91
    eval {
92
        if ( !$resultset->{results}->record_immediate( $index ) ) {
93
            my $start = $num_to_prefetch ? int( $index / $num_to_prefetch ) * $num_to_prefetch : $index;
94
95
            if ( $start + $num_to_prefetch >= $resultset->{results}->size() ) {
96
                $num_to_prefetch = $resultset->{results}->size() - $start;
97
            }
98
99
            $self->_log_debug("    fetch uncached, fetching $num_to_prefetch records starting at $start");
100
101
            $resultset->{results}->records( $start, $num_to_prefetch, 0 );
102
        }
103
104
        $record = $resultset->{results}->record_immediate( $index )->raw();
105
    };
106
    if ($@) {
107
        die $@ if ( ref($@) ne 'ZOOM::Exception' );
108
        $self->_set_error_from_zoom( $args, $@ );
109
        return;
110
    } else {
111
        return $record;
112
    }
113
}
114
115
sub close_handler {
116
    my ( $self, $args ) = @_;
117
118
    foreach my $resultset ( values %{ $self->{resultsets} } ) {
119
        $resultset->{results}->destroy();
120
    }
121
}
122
123
1;
(-)a/etc/z3950/attribute_mappings.yaml (+46 lines)
Line 0 Link Here
1
---
2
# Mappings from Z39.50 USE attributes to Koha search fields
3
authorities:
4
    # BIB-1 use attributes to index fields
5
    use:
6
        1: Personal-name
7
        2: Heading
8
        3: Heading
9
        9: LC-card-number
10
        12: Local-number
11
        default: _all
12
biblios:
13
    # BIB-1 use attributes to index fields
14
    use:
15
        1: author
16
        2: author
17
        3: author
18
        4: title
19
        5: se
20
        7: isbn
21
        8: issn
22
        9: LC-card-number
23
        10: bnb-card-number
24
        11: bgf-number
25
        12: Local-number
26
        20: local-classification
27
        21: subject
28
        30:
29
            - acqdate
30
            - copydate
31
            - pubdate
32
        31: pubdate
33
        32: acqdate
34
        52: control-number
35
        1003: author
36
        1007: identifier-standard
37
        1011: date-entered-on-file
38
        1012: date-time-last-modified
39
        1018: publisher
40
        1019: record-source
41
        1021: bib-level
42
        1028: barcode
43
        1031: itype
44
        1033: Host-Item-Number
45
        1045: control-number
46
        default: _all
(-)a/etc/z3950/config.xml (+12 lines)
Line 0 Link Here
1
<yazgfs>
2
  <server>
3
    <cql2rpn>pqf.properties</cql2rpn>
4
    <explain xmlns="http://explain.z3950.org/dtd/2.0/">
5
      <retrievalinfo>
6
        <retrieval syntax="usmarc" name="marc21"/>
7
        <retrieval syntax="unimarc" name="unimarc"/>
8
        <retrieval syntax="xml" name="marcxml" identifier="info:srw/schema/1/marcxml-v1.1"/>
9
      </retrievalinfo>
10
    </explain>
11
  </server>
12
</yazgfs>
(-)a/etc/z3950/pqf.properties (+163 lines)
Line 0 Link Here
1
#
2
# Propeties file to drive org.z3950.zing.cql.CQLNode's toPQF()
3
# back-end and the YAZ CQL-to-PQF converter.  This specifies the
4
# interpretation of various CQL indexes, relations, etc. in terms
5
# of Type-1 query attributes.
6
#
7
# This configuration file generates queries using BIB-1 attributes.
8
# See http://www.loc.gov/z3950/agency/zing/cql/dc-indexes.html
9
# for the Maintenance Agency's work-in-progress mapping of Dublin Core
10
# indexes to Attribute Architecture (util, XD and BIB-2)
11
# attributes.
12
13
# Identifiers for prefixes used in this file. (index.*)
14
set.cql     = info:srw/cql-context-set/1/cql-v1.1
15
set.rec     = info:srw/cql-context-set/2/rec-1.0
16
set.dc      = info:srw/cql-context-set/1/dc-v1.1
17
set.bath    = http://zing.z3950.org/cql/bath/2.0/
18
19
# default set (in query)
20
set     = info:srw/cql-context-set/1/dc-v1.1
21
22
# The default access point and result-set references
23
index.cql.serverChoice = 1=1016
24
    # srw.serverChoice is deprecated in favour of cql.serverChoice
25
    # BIB-1 "any"
26
27
index.rec.id                = 1=12
28
index.dc.identifier         = 1=1007
29
index.dc.title              = 1=4
30
index.dc.subject            = 1=21
31
index.dc.creator            = 1=1003
32
index.dc.author             = 1=1003
33
index.dc.itemtype           = 1=1031
34
index.dc.barcode            = 1=1028
35
index.dc.branch             = 1=1033
36
index.dc.isbn               = 1=7
37
index.dc.issn               = 1=8
38
index.dc.any                = 1=1016
39
index.dc.note               = 1=63
40
41
# personal name experimental
42
index.dc.pname  = 1=1
43
    ### Unofficial synonym for "creator"
44
index.dc.editor             = 1=1020
45
index.dc.publisher          = 1=1018
46
index.dc.description        = 1=62
47
    # "abstract"
48
index.dc.date               = 1=30
49
index.dc.resourceType       = 1=1031
50
    # guesswork: "Material-type"
51
index.dc.format             = 1=1034
52
    # guesswork: "Content-type"
53
index.dc.resourceIdentifier = 1=12
54
    # "Local number"
55
#index.dc.source                = 1=1019
56
    # "Record-source"
57
index.dc.language           = 1=54
58
    # "Code--language"
59
60
index.dc.Place-publication  = 1=59
61
    # "Place-publication"
62
63
#index.dc.relation           = 1=?
64
    ### No idea how to represent this
65
#index.dc.coverage           = 1=?
66
    ### No idea how to represent this
67
#index.dc.rights             = 1=?
68
    ### No idea how to represent this
69
70
# Relation attributes are selected according to the CQL relation by
71
# looking up the "relation.<relation>" property:
72
#
73
relation.<                  = 2=1
74
relation.le                 = 2=2
75
relation.eq                 = 2=3
76
relation.exact              = 2=3
77
relation.ge                 = 2=4
78
relation.>                  = 2=5
79
relation.<>                 = 2=6
80
81
### These two are not really right:
82
relation.all                = 2=3
83
relation.any                = 2=3
84
85
# BIB-1 doesn't have a server choice relation, so we just make the
86
# choice here, and use equality (which is clearly correct).
87
relation.scr                = 2=3
88
89
# Relation modifiers.
90
#
91
relationModifier.relevant   = 2=102
92
relationModifier.fuzzy      = 5=103
93
    ### 100 is "phonetic", which is not quite the same thing
94
relationModifier.stem       = 2=101
95
relationModifier.phonetic   = 2=100
96
97
# Position attributes may be specified for anchored terms (those
98
# beginning with "^", which is stripped) and unanchored (those not
99
# beginning with "^").  This may change when we get a BIB-1 truncation
100
# attribute that says "do what CQL does".
101
#
102
position.first              = 3=1 6=1
103
    # "first in field"
104
position.any                = 3=3 6=1
105
    # "any position in field"
106
position.last               = 3=4 6=1
107
    # not a standard BIB-1 attribute
108
position.firstAndLast       = 3=3 6=3
109
    # search term is anchored to be complete field
110
111
# Structure attributes may be specified for individual relations; a
112
# default structure attribute my be specified by the pseudo-relation
113
# "*", to be used whenever a relation not listed here occurs.
114
#
115
structure.exact             = 4=108
116
    # string
117
structure.all               = 4=2
118
structure.any               = 4=2
119
structure.*                 = 4=1
120
    # phrase
121
122
# Truncation attributes used to implement CQL wildcard patterns.  The
123
# simpler forms, left, right- and both-truncation will be used for the
124
# simplest patterns, so that we produce PQF queries that conform more
125
# closely to the Bath Profile.  However, when a more complex pattern
126
# such as "foo*bar" is used, we fall back on Z39.58-style masking.
127
#
128
truncation.right            = 5=1
129
truncation.left             = 5=2
130
truncation.both             = 5=3
131
truncation.none             = 5=100
132
truncation.z3958            = 5=104
133
134
# Finally, any additional attributes that should always be included
135
# with each term can be specified in the "always" property.
136
#
137
always                      = 6=1
138
# 6=1: completeness = incomplete subfield
139
140
141
# Bath Profile support, added Thu Dec 18 13:06:20 GMT 2003
142
# See the Bath Profile for SRW at
143
#   http://zing.z3950.org/cql/bath.html
144
# including the Bath Context Set defined within that document.
145
#
146
# In this file, we only map index-names to BIB-1 use attributes, doing
147
# so in accordance with the specifications of the Z39.50 Bath Profile,
148
# and leaving the relations, wildcards, etc. to fend for themselves.
149
150
index.bath.keyTitle         = 1=33
151
index.bath.possessingInstitution    = 1=1044
152
index.bath.name             = 1=1002
153
index.bath.personalName     = 1=1
154
index.bath.corporateName    = 1=2
155
index.bath.conferenceName   = 1=3
156
index.bath.uniformTitle     = 1=6
157
index.bath.isbn             = 1=7
158
index.bath.issn             = 1=8
159
index.bath.geographicName   = 1=58
160
index.bath.notes            = 1=63
161
index.bath.topicalSubject   = 1=1079
162
index.bath.genreForm        = 1=1075
163
(-)a/misc/bin/koha-zebra-ctl-oma.sh (-88 lines)
Lines 1-88 Link Here
1
#!/bin/bash
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
# along with Koha; if not, see <http://www.gnu.org/licenses>.
17
18
### BEGIN INIT INFO
19
# Provides:          koha-zebra-daemon
20
# Required-Start:    $syslog $remote_fs
21
# Required-Stop:     $syslog $remote_fs
22
# Default-Start:     2 3 4 5
23
# Default-Stop:      0 1 6
24
# Short-Description: Zebra server daemon for Koha indexing
25
### END INIT INFO
26
27
USER=ere
28
GROUP=users
29
DBNAME=koha3
30
NAME=koha-zebra-ctl.koha3
31
LOGDIR=/home/ere/koha-dev/var/log
32
ERRLOG=$LOGDIR/koha-zebradaemon.err
33
STDOUT=$LOGDIR/koha-zebradaemon.log
34
OUTPUT=$LOGDIR/koha-zebradaemon-output.log
35
KOHA_CONF=/home/ere/koha-dev/etc/koha-conf.xml
36
RUNDIR=/home/ere/koha-dev/var/run
37
LOCKDIR=/home/ere/koha-dev/var/lock
38
# you may need to change this depending on where zebrasrv is installed
39
ZEBRASRV=/usr/bin/zebrasrv
40
ZEBRAOPTIONS="-v none,fatal,warn"
41
42
test -f $ZEBRASRV || exit 0
43
44
OTHERUSER=''
45
if [[ $EUID -eq 0 ]]; then
46
    OTHERUSER="--user=$USER.$GROUP"
47
fi
48
49
case "$1" in
50
    start)
51
      echo "Starting Zebra Server"
52
53
      # create run and lock directories if needed;
54
      # /var/run and /var/lock are completely cleared at boot
55
      # on some platforms
56
      if [[ ! -d $RUNDIR ]]; then
57
        umask 022
58
        mkdir -p $RUNDIR
59
        if [[ $EUID -eq 0 ]]; then
60
            chown $USER:$GROUP $RUNDIR
61
        fi
62
      fi
63
      if [[ ! -d $LOCKDIR ]]; then
64
        umask 022
65
        mkdir -p $LOCKDIR
66
        mkdir -p $LOCKDIR/biblios
67
        mkdir -p $LOCKDIR/authorities
68
        mkdir -p $LOCKDIR/rebuild
69
        if [[ $EUID -eq 0 ]]; then
70
            chown -R $USER:$GROUP $LOCKDIR
71
        fi
72
      fi
73
74
      daemon --name=$NAME --errlog=$ERRLOG --stdout=$STDOUT --output=$OUTPUT --verbose=1 --respawn --delay=30 $OTHERUSER -- $ZEBRASRV $ZEBRAOPTIONS -f $KOHA_CONF 
75
      ;;
76
    stop)
77
      echo "Stopping Zebra Server"
78
      daemon --name=$NAME --errlog=$ERRLOG --stdout=$STDOUT --output=$OUTPUT --verbose=1 --respawn --delay=30 $OTHERUSER --stop -- $ZEBRASRV -f $KOHA_CONF 
79
      ;;
80
    restart)
81
      echo "Restarting the Zebra Server"
82
      daemon --name=$NAME --errlog=$ERRLOG --stdout=$STDOUT --output=$OUTPUT --verbose=1 --respawn --delay=30 $OTHERUSER --restart -- $ZEBRASRV -f $KOHA_CONF 
83
      ;;
84
    *)
85
      echo "Usage: /etc/init.d/$NAME {start|stop|restart}"
86
      exit 1
87
      ;;
88
esac
(-)a/misc/z3950_responder.pl (-5 / +20 lines)
Lines 20-29 Link Here
20
use Modern::Perl;
20
use Modern::Perl;
21
21
22
use Carp;
22
use Carp;
23
use File::Basename;
23
use Getopt::Long qw(:config no_ignore_case);
24
use Getopt::Long qw(:config no_ignore_case);
24
use Pod::Usage;
25
use Pod::Usage;
25
26
26
use C4::Context;
27
use Koha::Config;
27
use Koha::Z3950Responder;
28
use Koha::Z3950Responder;
28
29
29
=head1 SYNOPSIS
30
=head1 SYNOPSIS
Lines 31-37 use Koha::Z3950Responder; Link Here
31
   z3950_responder.pl [-h|--help] [--man] [-a <pdufile>] [-v <loglevel>] [-l <logfile>] [-u <user>]
32
   z3950_responder.pl [-h|--help] [--man] [-a <pdufile>] [-v <loglevel>] [-l <logfile>] [-u <user>]
32
                      [-c <config>] [-t <minutes>] [-k <kilobytes>] [-d <daemon>] [-p <pidfile>]
33
                      [-c <config>] [-t <minutes>] [-k <kilobytes>] [-d <daemon>] [-p <pidfile>]
33
                      [-C certfile] [-zKiDST1] [-m <time-format>] [-w <directory>] [--debug]
34
                      [-C certfile] [-zKiDST1] [-m <time-format>] [-w <directory>] [--debug]
34
                      [--add-item-status=SUBFIELD] [--prefetch=NUM_RECORDS]
35
                      [--add-item-status=SUBFIELD] [--prefetch=NUM_RECORDS] [--config-dir=<directory>]
35
                      [<listener-addr>... ]
36
                      [<listener-addr>... ]
36
37
37
=head1 OPTIONS
38
=head1 OPTIONS
Lines 66-71 status string. Link Here
66
67
67
Number of records to prefetch from Zebra. Defaults to 20.
68
Number of records to prefetch from Zebra. Defaults to 20.
68
69
70
=item B<--config-dir=directory>
71
72
Directory where to find configuration files required for proper operation. Defaults to z3950 under
73
the Koha config directory.
74
69
=back
75
=back
70
76
71
=head1 CONFIGURATION
77
=head1 CONFIGURATION
Lines 101-106 my $debug = 0; Link Here
101
my $help;
107
my $help;
102
my $man;
108
my $man;
103
my $prefetch = 20;
109
my $prefetch = 20;
110
my $config_dir = '';
111
104
my @yaz_options;
112
my @yaz_options;
105
113
106
sub add_yaz_option {
114
sub add_yaz_option {
Lines 122-127 GetOptions( Link Here
122
    '--add-item-status=s' => \$add_item_status_subfield,
130
    '--add-item-status=s' => \$add_item_status_subfield,
123
    '--add-status-multi-subfield' => \$add_status_multi_subfield,
131
    '--add-status-multi-subfield' => \$add_status_multi_subfield,
124
    '--prefetch=i' => \$prefetch,
132
    '--prefetch=i' => \$prefetch,
133
    '--config-dir=s' => \$config_dir,
125
    # Pass through YAZ options.
134
    # Pass through YAZ options.
126
    'a=s' => \&add_yaz_option,
135
    'a=s' => \&add_yaz_option,
127
    'v=s' => \&add_yaz_option,
136
    'v=s' => \&add_yaz_option,
Lines 152-166 if (!@ARGV || $ARGV[-1] =~ /^-/) { Link Here
152
    push(@ARGV, '@:2100');
161
    push(@ARGV, '@:2100');
153
}
162
}
154
163
155
# Create and start the server.
164
# If config_dir is not defined, default to z3950 under the Koha config directory
165
if (!$config_dir) {
166
    (undef, $config_dir) = fileparse(Koha::Config->guess_koha_conf);
167
    $config_dir .= 'z3950/';
168
} else {
169
    $config_dir .= '/' if ($config_dir !~ /\/$/);
170
}
156
171
157
die "This tool only works with Zebra" if C4::Context->preference('SearchEngine') ne 'Zebra';
172
# Create and start the server.
158
173
159
my $z = Koha::Z3950Responder->new( {
174
my $z = Koha::Z3950Responder->new( {
160
    add_item_status_subfield => $add_item_status_subfield,
175
    add_item_status_subfield => $add_item_status_subfield,
161
    add_status_multi_subfield => $add_status_multi_subfield,
176
    add_status_multi_subfield => $add_status_multi_subfield,
162
    debug => $debug,
177
    debug => $debug,
163
    num_to_prefetch => $prefetch,
178
    num_to_prefetch => $prefetch,
179
    config_dir => $config_dir,
164
    yaz_options => [ @yaz_options, @ARGV ],
180
    yaz_options => [ @yaz_options, @ARGV ],
165
} );
181
} );
166
182
167
- 

Return to bug 13937