3 package Koha
::Z3950Responder
::Session
;
5 # Copyright ByWater Solutions 2016
7 # This file is part of Koha.
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
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.
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.
24 use C4
::Circulation
qw( GetTransfers );
26 use C4
::Reserves
qw( GetReserveStatus );
34 Koha::Z3950Responder::Session
38 An abstract class where backend-specific session modules are derived from.
39 Z3950Responder creates one of the child classes depending on the SearchEngine
44 This class contains common functions for handling searching for and fetching
45 of records. It can optionally add item status information to the returned
46 records. The backend-specific abstract methods need to be implemented in a
51 OIDs and diagnostic codes used in Z39.50
56 UNIMARC_OID
=> '1.2.840.10003.5.1',
57 USMARC_OID
=> '1.2.840.10003.5.10',
58 MARCXML_OID
=> '1.2.840.10003.5.109.10'
62 ERR_TEMPORARY_ERROR
=> 2,
63 ERR_PRESENT_OUT_OF_RANGE
=> 13,
64 ERR_RECORD_TOO_LARGE
=> 16,
65 ERR_NO_SUCH_RESULTSET
=> 30,
66 ERR_SEARCH_FAILED
=> 125,
67 ERR_SYNTAX_UNSUPPORTED
=> 239,
68 ERR_DB_DOES_NOT_EXIST
=> 235,
73 =head2 INSTANCE METHODS
77 my $session = $self->new({
78 server => $z3950responder,
87 my ( $class, $args ) = @_;
91 logger
=> Koha
::Logger
->get({ interface
=> 'z3950' }),
95 if ( $self->{server
}->{debug
} ) {
96 $self->{logger
}->debug_to_screen();
99 $self->log_info('connected');
104 =head3 search_handler
106 Callback that is called when a new search is performed
108 Calls C<start_search> for backend-specific retrieval logic
113 my ( $self, $args ) = @_;
115 my $database = $args->{DATABASES
}->[0];
117 if ( $database ne $Koha::SearchEngine
::BIBLIOS_INDEX
&& $database ne $Koha::SearchEngine
::AUTHORITIES_INDEX
) {
118 $self->set_error( $args, $self->ERR_DB_DOES_NOT_EXIST, 'No such database' );
122 my $query = $args->{QUERY
};
123 $self->log_info("received search for '$query', (RS $args->{SETNAME})");
125 my ($resultset, $hits) = $self->start_search( $args, $self->{server
}->{num_to_prefetch
} );
126 return unless $resultset;
128 $args->{HITS
} = $hits;
129 $self->{resultsets
}->{ $args->{SETNAME
} } = $resultset;
134 Callback that is called when records are requested
136 Calls C<fetch_record> for backend-specific retrieval logic
141 my ( $self, $args ) = @_;
143 $self->log_debug("received fetch for RS $args->{SETNAME}, record $args->{OFFSET}");
145 my $server = $self->{server
};
147 my $form_oid = $args->{REQ_FORM
} // '';
148 my $composition = $args->{COMP
} // '';
149 $self->log_debug(" form OID '$form_oid', composition '$composition'");
151 my $resultset = $self->{resultsets
}->{ $args->{SETNAME
} };
152 # The offset comes across 1-indexed.
153 my $offset = $args->{OFFSET
} - 1;
155 return unless $self->check_fetch( $resultset, $args, $offset, 1 );
157 $args->{LAST
} = 1 if ( $offset == $resultset->{hits
} - 1 );
159 my $record = $self->fetch_record( $resultset, $args, $offset, $server->{num_to_prefetch
} );
160 return unless $record;
162 # Note that new_record_from_zebra is badly named and works also with Elasticsearch
163 $record = C4
::Search
::new_record_from_zebra
(
164 $resultset->{database
} eq 'biblios' ?
'biblioserver' : 'authorityserver',
168 if ( $server->{add_item_status_subfield
} ) {
169 my $tag = $server->{item_tag
};
171 foreach my $field ( $record->field($tag) ) {
172 $self->add_item_status( $field );
176 if ( $form_oid eq $self->MARCXML_OID && $composition eq 'marcxml' ) {
177 $args->{RECORD
} = $record->as_xml_record();
178 } elsif ( ( $form_oid eq $self->USMARC_OID || $form_oid eq $self->UNIMARC_OID ) && ( !$composition || $composition eq 'F' ) ) {
179 $args->{RECORD
} = $record->as_usmarc();
181 $self->set_error( $args, $self->ERR_SYNTAX_UNSUPPORTED, "Unsupported syntax/composition $form_oid/$composition" );
188 Callback that is called when a session is terminated
193 my ( $self, $args ) = @_;
195 # Override in a child class to add functionality
200 my ($resultset, $hits) = $self->_start_search( $args, $self->{server}->{num_to_prefetch} );
202 A backend-specific method for starting a new search
207 die('Abstract method');
212 $self->check_fetch($resultset, $args, $offset, $num_records);
214 Check that the fetch request parameters are within bounds of the result set.
219 my ( $self, $resultset, $args, $offset, $num_records ) = @_;
221 if ( !defined( $resultset ) ) {
222 $self->set_error( $args, ERR_NO_SUCH_RESULTSET
, 'No such resultset' );
226 if ( $offset < 0 || $offset + $num_records > $resultset->{hits
} ) {
227 $self->set_error( $args, ERR_PRESENT_OUT_OF_RANGE
, 'Present request out of range' );
236 my $record = $self->_fetch_record( $resultset, $args, $offset, $server->{num_to_prefetch} );
238 A backend-specific method for fetching a record
243 die('Abstract method');
246 =head3 add_item_status
248 $self->add_item_status( $field );
250 Add item status to the given field
254 sub add_item_status
{
255 my ( $self, $field ) = @_;
257 my $server = $self->{server
};
259 my $itemnumber_subfield = $server->{itemnumber_subfield
};
260 my $add_subfield = $server->{add_item_status_subfield
};
261 my $status_strings = $server->{status_strings
};
263 my $itemnumber = $field->subfield($itemnumber_subfield);
264 next unless $itemnumber;
266 my $item = Koha
::Items
->find( $itemnumber );
271 if ( $item->onloan() ) {
272 push @statuses, $status_strings->{CHECKED_OUT
};
275 if ( $item->itemlost() ) {
276 push @statuses, $status_strings->{LOST
};
279 if ( $item->notforloan() ) {
280 push @statuses, $status_strings->{NOT_FOR_LOAN
};
283 if ( $item->damaged() ) {
284 push @statuses, $status_strings->{DAMAGED
};
287 if ( $item->withdrawn() ) {
288 push @statuses, $status_strings->{WITHDRAWN
};
291 if ( scalar( GetTransfers
( $itemnumber ) ) ) {
292 push @statuses, $status_strings->{IN_TRANSIT
};
295 if ( GetReserveStatus
( $itemnumber ) ne '' ) {
296 push @statuses, $status_strings->{ON_HOLD
};
299 $field->delete_subfield( code
=> $itemnumber_subfield );
301 if ( $server->{add_status_multi_subfield
} ) {
302 $field->add_subfields( map { ( $add_subfield, $_ ) } ( @statuses ?
@statuses : $status_strings->{AVAILABLE
} ) );
304 $field->add_subfields( $add_subfield, @statuses ?
join( ', ', @statuses ) : $status_strings->{AVAILABLE
} );
311 $self->log_debug('Message');
313 Output a debug message
318 my ( $self, $msg ) = @_;
319 $self->{logger
}->debug("[$self->{peer}] $msg");
324 $self->log_info('Message');
326 Output an info message
331 my ( $self, $msg ) = @_;
332 $self->{logger
}->info("[$self->{peer}] $msg");
337 $self->log_error('Message');
339 Output an error message
344 my ( $self, $msg ) = @_;
345 $self->{logger
}->error("[$self->{peer}] $msg");
350 $self->set_error($args, $self->ERR_SEARCH_FAILED, 'Backend connection failed' );
352 Set and log an error code and diagnostic message to be returned to the client
357 my ( $self, $args, $code, $msg ) = @_;
359 ( $args->{ERR_CODE
}, $args->{ERR_STR
} ) = ( $code, $msg );
361 $self->log_error(" returning error $code: $msg");