Koha/Koha/Z3950Responder/Session.pm
Jonathan Druart 7d8b96803f
Bug 24545: Fix license statements
Bug 9978 should have fixed them all, but some were missing.
We want all the license statements part of Koha to be identical, and
using the GPLv3 statement.

Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: Martin Renvoize <martin.renvoize@ptfs-europe.com>
2020-02-24 13:31:26 +00:00

364 lines
8.5 KiB
Perl

#!/usr/bin/perl
package Koha::Z3950Responder::Session;
# Copyright ByWater Solutions 2016
#
# This file is part of Koha.
#
# Koha is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# Koha is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Koha; if not, see <http://www.gnu.org/licenses>.
use Modern::Perl;
use C4::Circulation qw( GetTransfers );
use C4::Context;
use C4::Reserves qw( GetReserveStatus );
use C4::Search qw();
use Koha::Items;
use Koha::Logger;
=head1 NAME
Koha::Z3950Responder::Session
=head1 SYNOPSIS
An abstract class where backend-specific session modules are derived from.
Z3950Responder creates one of the child classes depending on the SearchEngine
preference.
=head1 DESCRIPTION
This class contains common functions for handling searching for and fetching
of records. It can optionally add item status information to the returned
records. The backend-specific abstract methods need to be implemented in a
child class.
=head2 CONSTANTS
OIDs and diagnostic codes used in Z39.50
=cut
use constant {
UNIMARC_OID => '1.2.840.10003.5.1',
USMARC_OID => '1.2.840.10003.5.10',
MARCXML_OID => '1.2.840.10003.5.109.10'
};
use constant {
ERR_TEMPORARY_ERROR => 2,
ERR_PRESENT_OUT_OF_RANGE => 13,
ERR_RECORD_TOO_LARGE => 16,
ERR_NO_SUCH_RESULTSET => 30,
ERR_SEARCH_FAILED => 125,
ERR_SYNTAX_UNSUPPORTED => 239,
ERR_DB_DOES_NOT_EXIST => 235,
};
=head1 FUNCTIONS
=head2 INSTANCE METHODS
=head3 new
my $session = $self->new({
server => $z3950responder,
peer => 'PEER NAME'
});
Instantiate a Session
=cut
sub new {
my ( $class, $args ) = @_;
my $self = bless( {
%$args,
logger => Koha::Logger->get({ interface => 'z3950' }),
resultsets => {},
}, $class );
if ( $self->{server}->{debug} ) {
$self->{logger}->debug_to_screen();
}
$self->log_info('connected');
return $self;
}
=head3 search_handler
Callback that is called when a new search is performed
Calls C<start_search> for backend-specific retrieval logic
=cut
sub search_handler {
my ( $self, $args ) = @_;
my $database = $args->{DATABASES}->[0];
if ( $database ne $Koha::SearchEngine::BIBLIOS_INDEX && $database ne $Koha::SearchEngine::AUTHORITIES_INDEX ) {
$self->set_error( $args, $self->ERR_DB_DOES_NOT_EXIST, 'No such database' );
return;
}
my $query = $args->{QUERY};
$self->log_info("received search for '$query', (RS $args->{SETNAME})");
my ($resultset, $hits) = $self->start_search( $args, $self->{server}->{num_to_prefetch} );
return unless $resultset;
$args->{HITS} = $hits;
$self->{resultsets}->{ $args->{SETNAME} } = $resultset;
}
=head3 fetch_handler
Callback that is called when records are requested
Calls C<fetch_record> for backend-specific retrieval logic
=cut
sub fetch_handler {
my ( $self, $args ) = @_;
$self->log_debug("received fetch for RS $args->{SETNAME}, record $args->{OFFSET}");
my $server = $self->{server};
my $form_oid = $args->{REQ_FORM} // '';
my $composition = $args->{COMP} // '';
$self->log_debug(" form OID '$form_oid', composition '$composition'");
my $resultset = $self->{resultsets}->{ $args->{SETNAME} };
# The offset comes across 1-indexed.
my $offset = $args->{OFFSET} - 1;
return unless $self->check_fetch( $resultset, $args, $offset, 1 );
$args->{LAST} = 1 if ( $offset == $resultset->{hits} - 1 );
my $record = $self->fetch_record( $resultset, $args, $offset, $server->{num_to_prefetch} );
return unless $record;
# Note that new_record_from_zebra is badly named and works also with Elasticsearch
$record = C4::Search::new_record_from_zebra(
$resultset->{database} eq 'biblios' ? 'biblioserver' : 'authorityserver',
$record
);
if ( $server->{add_item_status_subfield} ) {
my $tag = $server->{item_tag};
foreach my $field ( $record->field($tag) ) {
$self->add_item_status( $field );
}
}
if ( $form_oid eq $self->MARCXML_OID && $composition eq 'marcxml' ) {
$args->{RECORD} = $record->as_xml_record();
} elsif ( ( $form_oid eq $self->USMARC_OID || $form_oid eq $self->UNIMARC_OID ) && ( !$composition || $composition eq 'F' ) ) {
$args->{RECORD} = $record->as_usmarc();
} else {
$self->set_error( $args, $self->ERR_SYNTAX_UNSUPPORTED, "Unsupported syntax/composition $form_oid/$composition" );
return;
}
}
=head3 close_handler
Callback that is called when a session is terminated
=cut
sub close_handler {
my ( $self, $args ) = @_;
# Override in a child class to add functionality
}
=head3 start_search
my ($resultset, $hits) = $self->_start_search( $args, $self->{server}->{num_to_prefetch} );
A backend-specific method for starting a new search
=cut
sub start_search {
die('Abstract method');
}
=head3 check_fetch
$self->check_fetch($resultset, $args, $offset, $num_records);
Check that the fetch request parameters are within bounds of the result set.
=cut
sub check_fetch {
my ( $self, $resultset, $args, $offset, $num_records ) = @_;
if ( !defined( $resultset ) ) {
$self->set_error( $args, ERR_NO_SUCH_RESULTSET, 'No such resultset' );
return 0;
}
if ( $offset < 0 || $offset + $num_records > $resultset->{hits} ) {
$self->set_error( $args, ERR_PRESENT_OUT_OF_RANGE, 'Present request out of range' );
return 0;
}
return 1;
}
=head3 fetch_record
my $record = $self->_fetch_record( $resultset, $args, $offset, $server->{num_to_prefetch} );
A backend-specific method for fetching a record
=cut
sub fetch_record {
die('Abstract method');
}
=head3 add_item_status
$self->add_item_status( $field );
Add item status to the given field
=cut
sub add_item_status {
my ( $self, $field ) = @_;
my $server = $self->{server};
my $itemnumber_subfield = $server->{itemnumber_subfield};
my $add_subfield = $server->{add_item_status_subfield};
my $status_strings = $server->{status_strings};
my $itemnumber = $field->subfield($itemnumber_subfield);
next unless $itemnumber;
my $item = Koha::Items->find( $itemnumber );
return unless $item;
my @statuses;
if ( $item->onloan() ) {
push @statuses, $status_strings->{CHECKED_OUT};
}
if ( $item->itemlost() ) {
push @statuses, $status_strings->{LOST};
}
if ( $item->notforloan() ) {
push @statuses, $status_strings->{NOT_FOR_LOAN};
}
if ( $item->damaged() ) {
push @statuses, $status_strings->{DAMAGED};
}
if ( $item->withdrawn() ) {
push @statuses, $status_strings->{WITHDRAWN};
}
if ( scalar( GetTransfers( $itemnumber ) ) ) {
push @statuses, $status_strings->{IN_TRANSIT};
}
if ( GetReserveStatus( $itemnumber ) ne '' ) {
push @statuses, $status_strings->{ON_HOLD};
}
$field->delete_subfield( code => $itemnumber_subfield );
if ( $server->{add_status_multi_subfield} ) {
$field->add_subfields( map { ( $add_subfield, $_ ) } ( @statuses ? @statuses : $status_strings->{AVAILABLE} ) );
} else {
$field->add_subfields( $add_subfield, @statuses ? join( ', ', @statuses ) : $status_strings->{AVAILABLE} );
}
}
=head3 log_debug
$self->log_debug('Message');
Output a debug message
=cut
sub log_debug {
my ( $self, $msg ) = @_;
$self->{logger}->debug("[$self->{peer}] $msg");
}
=head3 log_info
$self->log_info('Message');
Output an info message
=cut
sub log_info {
my ( $self, $msg ) = @_;
$self->{logger}->info("[$self->{peer}] $msg");
}
=head3 log_error
$self->log_error('Message');
Output an error message
=cut
sub log_error {
my ( $self, $msg ) = @_;
$self->{logger}->error("[$self->{peer}] $msg");
}
=head3 set_error
$self->set_error($args, $self->ERR_SEARCH_FAILED, 'Backend connection failed' );
Set and log an error code and diagnostic message to be returned to the client
=cut
sub set_error {
my ( $self, $args, $code, $msg ) = @_;
( $args->{ERR_CODE}, $args->{ERR_STR} ) = ( $code, $msg );
$self->log_error(" returning error $code: $msg");
}
1;