##
-## Copyright (c) 2000, Index Data.
+## Copyright (c) 2000-2006, Index Data.
##
## Permission to use, copy, modify, distribute, and sell this software and
## its documentation, in whole or in part, for any purpose, is hereby granted,
##
##
-## $Log: SimpleServer.pm,v $
-## Revision 1.14 2002-03-06 11:30:02 mike
-## Add RPN structure documentation to SimpleServer.pm's POD.
-## Add README to MANIFEST.
-##
-## Revision 1.13 2002/03/06 11:02:04 mike
-## Added simple README file, derived from POD comments in SimpleServer.pm
-## Fixed my (Mike Taylor's) email address
-##
-## Revision 1.12 2002/03/05 20:52:22 sondberg
-## Version 0.05 so that we can release the thing at CPAN.
-##
-## Revision 1.11 2002/03/05 20:49:56 sondberg
-## Added a couple of lines of documentation.
-##
-## Revision 1.10 2002/02/28 11:21:57 mike
-## Add RPN structure to search-handler argument hash.
-##
-## Revision 1.9 2001/08/29 11:48:36 sondberg
-## Added routines
-##
-## Net::Z3950::SimpleServer::ScanSuccess
-## Net::Z3950::SimpleServer::ScanPartial
-##
-## and a bit of documentation.
-##
-## Revision 1.8 2001/08/29 10:29:51 sondberg
-## Added some documentation of scan.
-##
-## Revision 1.7 2001/08/24 14:00:20 sondberg
-## Added support for scan.
-##
-## Revision 1.6 2001/03/13 14:17:15 sondberg
-## Added support for GRS-1.
-##
+## $Id: SimpleServer.pm,v 1.47 2008-04-03 10:46:46 mike Exp $
package Net::Z3950::SimpleServer;
require AutoLoader;
@ISA = qw(Exporter AutoLoader DynaLoader);
-# Items to export into callers namespace by default. Note: do not export
-# names by default without a very good reason. Use EXPORT_OK instead.
-# Do not simply export all your public functions/methods/constants.
-@EXPORT = qw(
-
-);
-$VERSION = '0.05';
+@EXPORT = qw( );
+$VERSION = '1.10';
bootstrap Net::Z3950::SimpleServer $VERSION;
my $self = shift;
my @args = @_;
+ ### This modal internal interface, in which we set a bunch of
+ # globals and then call start_server(), is asking for
+ # trouble. Instead, we should just pass the $self object
+ # as a parameter into start_server().
+ if (defined($self->{GHANDLE})) {
+ set_ghandle($self->{GHANDLE});
+ }
if (defined($self->{INIT})) {
set_init_handler($self->{INIT});
}
if (defined($self->{SCAN})) {
set_scan_handler($self->{SCAN});
}
+ if (defined($self->{SORT})) {
+ set_sort_handler($self->{SORT});
+ }
+ if (defined($self->{EXPLAIN})) {
+ set_explain_handler($self->{EXPLAIN});
+ }
+ if (defined($self->{DELETE})) {
+ set_delete_handler($self->{DELETE});
+ }
start_server(@args);
}
# Register packages that we will use in translated RPNs
+package Net::Z3950::RPN::Node;
package Net::Z3950::APDU::Query;
+our @ISA = qw(Net::Z3950::RPN::Node);
package Net::Z3950::APDU::OID;
package Net::Z3950::RPN::And;
+our @ISA = qw(Net::Z3950::RPN::Node);
package Net::Z3950::RPN::Or;
+our @ISA = qw(Net::Z3950::RPN::Node);
package Net::Z3950::RPN::AndNot;
+our @ISA = qw(Net::Z3950::RPN::Node);
package Net::Z3950::RPN::Term;
+our @ISA = qw(Net::Z3950::RPN::Node);
+package Net::Z3950::RPN::RSID;
+our @ISA = qw(Net::Z3950::RPN::Node);
package Net::Z3950::RPN::Attributes;
package Net::Z3950::RPN::Attribute;
+
+# Utility method for re-rendering Type-1 query back down to PQF
+package Net::Z3950::RPN::Node;
+
+sub toPQF {
+ my $this = shift();
+ my $class = ref $this;
+
+ if ($class eq "Net::Z3950::APDU::Query") {
+ my $res = "";
+ my $set = $this->{attributeSet};
+ $res .= "\@attrset $set " if defined $set;
+ return $res . $this->{query}->toPQF();
+ } elsif ($class eq "Net::Z3950::RPN::Or") {
+ return '@or ' . $this->[0]->toPQF() . ' ' . $this->[1]->toPQF();
+ } elsif ($class eq "Net::Z3950::RPN::And") {
+ return '@and ' . $this->[0]->toPQF() . ' ' . $this->[1]->toPQF();
+ } elsif ($class eq "Net::Z3950::RPN::AndNot") {
+ return '@not ' . $this->[0]->toPQF() . ' ' . $this->[1]->toPQF();
+ } elsif ($class eq "Net::Z3950::RPN::RSID") {
+ return '@set ' . $this->{id};
+ } elsif ($class ne "Net::Z3950::RPN::Term") {
+ die "unknown PQF node-type '$class'";
+ }
+
+ my $res = "";
+ foreach my $attr (@{ $this->{attributes} }) {
+ $res .= "\@attr ";
+ my $set = $attr->{attributeSet};
+ $res .= "$set " if defined $set;
+ $res .= $attr->{attributeType} . "=" . $attr->{attributeValue} . " ";
+ }
+
+ return $res . $this->{term};
+}
+
+
# Must revert to original package for Autoloader's benefit
package Net::Z3950::SimpleServer;
}
}
-
## Register custom event handlers:
+ my $z = new Net::Z3950::SimpleServer(GHANDLE = $someObject,
+ INIT => \&my_init_handler,
+ CLOSE => \&my_close_handler,
+ SEARCH => \&my_search_handler,
+ FETCH => \&my_fetch_handler);
- my $z = new Net::Z3950::SimpleServer( INIT => \&my_init_handler,
- CLOSE => \&my_close_handler,
- SEARCH => \&my_search_handler,
- FETCH => \&my_fetch_handler);
## Launch server:
-
$z->launch_server("ztest.pl", @ARGV);
=head1 DESCRIPTION
the Perl script to the server. The server calls the registered
subroutines to field incoming requests from Z39.50 clients.
-A reference to an anonymous hash is passed to each handle. Some of
+A reference to an anonymous hash is passed to each handler. Some of
the entries of these hashes are to be considered input and others
output parameters.
-The Perl programmer specifies the event handles for the server by
-means of the the SimpleServer object constructor
+The Perl programmer specifies the event handlers for the server by
+means of the SimpleServer object constructor
my $z = new Net::Z3950::SimpleServer(
INIT => \&my_init_handler,
SEARCH => \&my_search_handler,
PRESENT => \&my_present_handler,
SCAN => \&my_scan_handler,
- FETCH => \&my_fetch_handler);
+ FETCH => \&my_fetch_handler,
+ EXPLAIN => \&my_explain_handler,
+ DELETE => \&my_delete_handler,
+ SORT => \&my_sort_handler);
+
+In addition, the arguments to the constructor may include GHANDLE, a
+global handle which is made available to each invocation of every
+callback function. This is typically a reference to either a hash or
+an object.
+
+If you want your SimpleServer to start a thread (threaded mode) to
+handle each incoming Z39.50 request instead of forking a process
+(forking mode), you need to register the handlers by symbol rather
+than by code reference. Thus, in threaded mode, you will need to
+register your handlers this way:
-After the custom event handles are declared, the server is launched
+ my $z = new Net::Z3950::SimpleServer(
+ INIT => "my_package::my_init_handler",
+ CLOSE => "my_package::my_close_handler",
+ ....
+ .... );
+
+where my_package is the Perl package in which your handler is
+located.
+
+After the custom event handlers are declared, the server is launched
by means of the method
$z->launch_server("MyServer.pl", @ARGV);
Notice, the first argument should be the name of your server
script (for logging purposes), while the rest of the arguments
are documented in the YAZ toolkit manual: The section on
-application invocation: <http://www.indexdata.dk/yaz/yaz-7.php>
+application invocation: <http://indexdata.com/yaz/doc/server.invocation.tkl>
+
+In particular, you need to use the -T switch to start your SimpleServer
+in threaded mode.
=head2 Init handler
$args = {
## Response parameters:
+ PEER_NAME => "", ## Name or IP address of connecting client
+ IMP_ID => "", ## Z39.50 Implementation ID
IMP_NAME => "", ## Z39.50 Implementation name
IMP_VER => "", ## Z39.50 Implementation version
ERR_CODE => 0, ## Error code, cnf. Z39.50 manual
+ ERR_STR => "", ## Error string (additional info.)
+ USER => "xxx" ## If Z39.50 authentication is used,
+ ## this member contains user name
+ PASS => "yyy" ## Under same conditions, this member
+ ## contains the password in clear text
+ GHANDLE => $obj ## Global handler specified at creation
HANDLE => undef ## Handler of Perl data structure
};
it is always best to store them in a private session structure -
rather than leaving them in global variables in your script.
-The Implementation name and version are only really used by Z39.50
+The Implementation ID, name and version are only really used by Z39.50
client developers to see what kind of server they're dealing with.
Filling these in is optional.
The ERR_CODE should be left at 0 (the default value) if you wish to
accept the connection. Any other value is interpreted as a failure
-and the client will be shown the door.
+and the client will be shown the door, with the code and the
+associated additional information, ERR_STR returned.
=head2 Search handler
$args = {
## Request parameters:
+ GHANDLE => $obj ## Global handler specified at creation
HANDLE => ref, ## Your session reference.
SETNAME => "id", ## ID of the result set
REPL_SET => 0, ## Replace set if already existing?
See below for details.
-=back
+=item C<Net::Z3950::RPN::RSID>
+
+A reference to a result-set ID indicating a previous search. The ID
+of the result-set is in the C<id> element.
-(I guess I should make a superclass C<Net::Z3950::RPN::Node> and make
-all of these subclasses of it. Not done that yet, but will do soon.)
+=back
=back
=item C<attributeValue>
-An integer indicating the value of the attribute - for example, under
+An integer or string indicating the value of the attribute - for example, under
BIB-1, if the attribute type is 1, then value 4 indictates a title
search and 7 indictates an ISBN search; but if the attribute type is
2, then value 4 indicates a ``greater than or equal'' search, and 102
=back
-Note that, at the moment, none of these classes have any methods at
+All of these classes except C<Attributes> and C<Attribute> are
+subclasses of the abstract class C<Net::Z3950::RPN::Node>. That class
+has a single method, C<toPQF()>, which may be used to turn an RPN
+tree, or part of one, back into a textual prefix query.
+
+Note that, apart to C<toPQF()>, none of these classes have any methods at
all: the blessing into classes is largely just a documentation thing
so that, for example, if you do
$args = {
## Client/server request:
+ GHANDLE => $obj ## Global handler specified at creation
HANDLE => ref, ## Reference to datastructure
SETNAME => "id", ## Result set ID
START => xxx, ## Start position
$args = {
## Client/server request:
+ GHANDLE => $obj ## Global handler specified at creation
HANDLE => ref ## Reference to data structure
SETNAME => "id" ## ID of the requested result set
OFFSET => nnn ## Record offset number
REQ_FORM => "n.m.k.l"## Client requested format OID
COMP => "xyz" ## Formatting instructions
+ SCHEMA => "abc" ## Requested schema, if any
## Handler response:
ERR_STR => "" ## Error string
SUR_FLAG => 0 ## Surrogate diagnostic flag
REP_FORM => "n.m.k.l"## Provided format OID
+ SCHEMA => "abc" ## Provided schema, if any
};
The REP_FORM value has by default the REQ_FORM value but can be set to
$args = {
## Client request
- HANDLE => $ref ## Reference to data structure
+ GHANDLE => $obj, ## Global handler specified at creation
+ HANDLE => $ref, ## Reference to data structure
+ DATABASES => ["xxx"], ## Reference to a list of data-
+ ## bases to search
TERM => 'start', ## The start term
+ RPN => $obj, ## Reference to a Net::Z3950::RPN::Term
+
NUMBER => xx, ## Number of requested terms
POS => yy, ## Position of starting point
## within returned list
...
];
-The $status flag should be assigned one of two values:
+The $status flag is only meaningful after a successful scan, and
+should be assigned one of two values:
- Net::Z3950::SimpleServer::ScanSuccess On success (default)
- Net::Z3950::SimpleServer::ScanPartial Less terms returned than requested
+ Net::Z3950::SimpleServer::ScanSuccess Full success (default)
+ Net::Z3950::SimpleServer::ScanPartial Fewer terms returned than requested
The STEP member contains the requested number of entries in the term-list
between two adjacent entries in the response.
+A better alternative to the TERM member is the the RPN
+member, which is a reference to a Net::Z3950::RPN::Term object
+representing the scan cloause. The structure of that object is the
+same as for Term objects included as part of the RPN tree passed to
+search handlers. This is more useful than the simple TERM because it
+includes attributes (e.g. access points associated with the term),
+which are discarded by the TERM element.
+
=head2 Close handler
-The argument hash recieved by the close handler has one element only:
+The argument hash recieved by the close handler has two elements only:
$args = {
## Server provides:
+
+ GHANDLE => $obj ## Global handler specified at creation
HANDLE => ref ## Reference to data structure
};
after this call. If you need to close down a connection to your server
or something similar, this is the place to do it.
+=head2 Delete handler
+
+The argument hash recieved by the delete handler has the following elements:
+
+ $args = {
+ ## Client request:
+ GHANDLE => $obj, ## Global handler specified at creation
+ HANDLE => ref, ## Reference to data structure
+ SETNAME => "id", ## Result set ID
+
+ ## Server response:
+ STATUS => 0 ## Deletion status
+ };
+
+The SETNAME element of the argument hash may or may not be defined.
+If it is, then SETNAME is the name of a result set to be deleted; if
+not, then all result-sets associated with the current session should
+be deleted. In either case, the callback function should report on
+success or failure by setting the STATUS element either to zero, on
+success, or to an integer from 1 to 10, to indicate one of the ten
+possible failure codes described in section 3.2.4.1.4 of the Z39.50
+standard -- see
+http://www.loc.gov/z3950/agency/markup/05.html#Delete-list-statuses1
+
+=head2 Sort handler
+
+The argument hash recieved by the sort handler has the following elements:
+
+ $args = {
+ ## Client request:
+ GHANDLE => $obj, ## Global handler specified at creation
+ HANDLE => ref, ## Reference to data structure
+ INPUT => [ a, b ... ], ## Names of result-sets to sort
+ OUTPUT => "name", ## Name of result-set to sort into
+ SEQUENCE ## Sort specification: see below
+
+ ## Server response:
+ STATUS => 0, ## Success, Partial or Failure
+ ERR_CODE => 0, ## Error code
+ ERR_STR => '', ## Diagnostic message
+
+ };
+
+The SEQUENCE element is a reference to an array, each element of which
+is a hash representing a sort key. Each hash contains the following
+elements:
+
+=over 4
+
+=item RELATION
+
+0 for an ascending sort, 1 for descending, 3 for ascending by
+frequency, or 4 for descending by frequency.
+
+=item CASE
+
+0 for a case-sensitive sort, 1 for case-insensitive
+
+=item MISSING
+
+How to respond if one or more records in the set to be sorted are
+missing the fields indicated in the sort specification. 1 to abort
+the sort, 2 to use a "null value", 3 if a value is provided to use in
+place of the missing data (although in the latter case, the actual
+value to use is currently not made available, so this is useless).
+
+=back
+
+And one or other of the following:
+
+=over 4
+
+=item SORTFIELD
+
+A string indicating the field to be sorted, which the server may
+interpret as it sees fit (presumably by an out-of-band agreement with
+the client).
+
+=item ELEMENTSPEC_TYPE and ELEMENTSPEC_VALUE
+
+I have no idea what this is.
+
+=item ATTRSET and SORT_ATTR
+
+ATTRSET is the attribute set from which the attributes are taken, and
+SORT_ATTR is a reference to an array containing the attributes
+themselves. Each attribute is represented by (are you following this
+carefully?) yet another hash, this one containing the elements
+ATTR_TYPE and ATTR_VALUE: for example, type=1 and value=4 in the BIB-1
+attribute set would indicate access-point 4 which is title, so that a
+sort of title is requested.
+
+=back
+
+Precisely why all of the above is so is not clear, but goes some way
+to explain why, in the Z39.50 world, the developers of the standard
+are not so much worshipped as blamed.
+
+The backend function should set STATUS to 0 on success, 1 for "partial
+success" (don't ask) or 2 on failure, in which case ERR_CODE and
+ERR_STR should be set.
+
+=head2 Support for SRU and SRW
+
+Since release 1.0, SimpleServer includes support for serving the SRU
+and SRW protocols as well as Z39.50. These ``web-friendly'' protocols
+enable similar functionality to that of Z39.50, but by means of rich
+URLs in the case of SRU, and a SOAP-based web-service in the case of
+SRW. These protocols are described at
+http://www.loc.gov/sru
+
+In order to serve these protocols from a SimpleServer-based
+application, it is necessary to launch the application with a YAZ
+Generic Frontend Server (GFS) configuration file, which can be
+specified using the command-line argument C<-f> I<filename>. A
+minimal configuration file looks like this:
+
+ <yazgfs>
+ <server>
+ <cql2rpn>pqf.properties</cql2rpn>
+ </server>
+ </yazgfs>
+
+This file specifies only that C<pqf.properties> should be used to
+translate the CQL queries of SRU and SRW into corresponding Z39.50
+Type-1 queries. For more information about YAZ GFS configuration,
+including how to specify an Explain record, see the I<Virtual Hosts>
+section of the YAZ manual at
+http://indexdata.com/yaz/doc/server.vhosts.tkl
+
+The mapping of CQL queries into Z39.50 Type-1 queries is specified by
+a file that indicates which BIB-1 attributes should be generated for
+each CQL index, relation, modifiers, etc. A typical section of this
+file looks like this:
+
+ index.dc.title = 1=4
+ index.dc.subject = 1=21
+ index.dc.creator = 1=1003
+ relation.< = 2=1
+ relation.le = 2=2
+
+This file specifies the BIB-1 access points (type=1) for the Dublin
+Core indexes C<title>, C<subject> and C<creator>, and the BIB-1
+relations (type=2) corresponding to the CQL relations C<E<lt>> and
+C<E<lt>=>. For more information about the format of this file, see
+the I<CQL> section of the YAZ manual at
+http://indexdata.com/yaz/doc/tools.tkl#tools.cql
+
+The YAZ distribution include a sample CQL-to-PQF mapping configuration
+file called C<pqf.properties>; this is sufficient for many
+applications, and a good base to work from for most others.
+
+If a SimpleServer-based application is run without this SRU-specific
+configuration, it can still serve SRU; however, CQL queries will not
+be translated, but passed straight through to the search-handler
+function, as the C<CQL> member of the parameters hash. It is then the
+responsibility of the back-end application to parse and handle the CQL
+query, which is most easily done using Ed Summers' fine C<CQL::Parser>
+module, available from CPAN at
+http://search.cpan.org/~esummers/CQL-Parser/
+
=head1 AUTHORS
-Anders Sønderberg (sondberg@indexdata.dk) and Sebastian Hammer
-(quinn@indexdata.dk). Substantial contributions made by Mike Taylor
-(mike@miketaylor.org.uk).
+Anders Sønderberg (sondberg@indexdata.dk),
+Sebastian Hammer (quinn@indexdata.dk),
+Mike Taylor (indexdata.com).
=head1 SEE ALSO