##
-## Copyright (c) 2000-2004, 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,
##
##
-## $Id: SimpleServer.pm,v 1.23 2006-03-24 01:21:15 mike Exp $
+## $Id: SimpleServer.pm,v 1.39 2007-08-20 10:59:50 mike Exp $
package Net::Z3950::SimpleServer;
@ISA = qw(Exporter AutoLoader DynaLoader);
@EXPORT = qw( );
-$VERSION = '1.00';
+$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
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
## 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
};
$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?
=back
-(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 one day.)
-
=back
=over 4
=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