##
-## 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.15 2002-09-16 14:00:16 sondberg
-## Updated Changes and added a few lines of documentation.
-##
-## 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.39 2007-08-20 10:59:50 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.06';
+@EXPORT = qw( );
+$VERSION = '1.07';
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});
+ }
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} }) {
+ use Data::Dumper; print "considering attr: ", Dumper($attr);
+ $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);
+
+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);
are documented in the YAZ toolkit manual: The section on
application invocation: <http://www.indexdata.dk/yaz/yaz-7.php>
+In particular, you need to use the -T switch to start your SimpleServer
+in threaded mode.
+
=head2 Init handler
The init handler is called whenever a Z39.50 client is attempting
$args = {
## Response parameters:
+ 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 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