Preparing for release.
[simpleserver-moved-to-github.git] / SimpleServer.pm
index b2c27a1..ff1e62f 100644 (file)
@@ -1,5 +1,5 @@
 ##
-##  Copyright (c) 2000, Index Data.
+##  Copyright (c) 2000-2004, 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,
@@ -25,6 +25,8 @@
 ##
 ##
 
+## $Id: SimpleServer.pm,v 1.20 2004-05-28 20:14:28 sondberg Exp $
+
 package Net::Z3950::SimpleServer;
 
 use strict;
@@ -36,13 +38,8 @@ require DynaLoader;
 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.02';
+@EXPORT = qw( );
+$VERSION = '0.08';
 
 bootstrap Net::Z3950::SimpleServer $VERSION;
 
@@ -52,18 +49,16 @@ my $count = 0;
 
 sub new {
        my $class = shift;
-       my $args = shift || croak "SimpleServer::new: Usage new(argument hash)";
-       my $self = {};
+       my %args = @_;
+       my $self = \%args;
 
        if ($count) {
                carp "SimpleServer.pm: WARNING: Multithreaded server unsupported";
        }
        $count = 1;
 
-       $self->{INIT} = $args->{INIT};
-       $self->{SEARCH} = $args->{SEARCH} || croak "SimpleServer.pm: ERROR: Unspecified search handler";
-       $self->{FETCH} = $args->{FETCH} || croak "SimpleServer.pm: ERROR: Unspecified fetch handler";
-       $self->{CLOSE} = $args->{CLOSE};
+       croak "SimpleServer.pm: ERROR: Unspecified search handler" unless defined($self->{SEARCH});
+       croak "SimpleServer.pm: ERROR: Unspecified fetch handler" unless defined($self->{FETCH});
 
        bless $self, $class;
        return $self;
@@ -82,11 +77,31 @@ sub launch_server {
        if (defined($self->{CLOSE})) {
                set_close_handler($self->{CLOSE});
        }
+       if (defined($self->{PRESENT})) {
+               set_present_handler($self->{PRESENT});
+       }
+       if (defined($self->{SCAN})) {
+               set_scan_handler($self->{SCAN});
+       }
 
        start_server(@args);
 }
 
 
+# Register packages that we will use in translated RPNs
+package Net::Z3950::APDU::Query;
+package Net::Z3950::APDU::OID;
+package Net::Z3950::RPN::And;
+package Net::Z3950::RPN::Or;
+package Net::Z3950::RPN::AndNot;
+package Net::Z3950::RPN::Term;
+package Net::Z3950::RPN::Attributes;
+package Net::Z3950::RPN::Attribute;
+
+# Must revert to original package for Autoloader's benefit
+package Net::Z3950::SimpleServer;
+
+
 # Autoload methods go after =cut, and are processed by the autosplit program.
 
 1;
@@ -119,7 +134,7 @@ Net::Z3950::SimpleServer - Simple Perl API for building Z39.50 servers.
 
        my $set_id = $args->{SETNAME};
 
-       my $record = fetch_a_record($args->{OFFSET);
+       my $record = fetch_a_record($args->{OFFSET});
 
        $args->{RECORD} = $record;
        if (number_of_hits() == $args->{OFFSET}) {      ## Last record in set?
@@ -132,15 +147,13 @@ Net::Z3950::SimpleServer - Simple Perl API for building Z39.50 servers.
 
   ## Register custom event handlers:
 
-  my $handle = Net::Z3950::SimpleServer->new({
-                                               INIT   =>  \&my_init_handler,
+  my $z = new Net::Z3950::SimpleServer(                INIT   =>  \&my_init_handler,
                                                CLOSE  =>  \&my_close_handler,
                                                SEARCH =>  \&my_search_handler,
-                                               FETCH  =>  \&my_fetch_handler
-                                            });
+                                               FETCH  =>  \&my_fetch_handler);
   ## Launch server:
 
-  $handle->launch_server("ztest.pl", @ARGV);
+  $z->launch_server("ztest.pl", @ARGV);
 
 =head1 DESCRIPTION
 
@@ -170,7 +183,9 @@ of events:
 
   - Initialize request
   - Search request
+  - Present request
   - Fetching of records
+  - Scan request (browsing) 
   - Closing down connection
 
 Note that only the Search and Fetch handler functions are required.
@@ -187,16 +202,18 @@ output parameters.
 The Perl programmer specifies the event handles for the server by
 means of the the SimpleServer object constructor
 
-  my $handle = Net::Z3950::SimpleServer->new({
-               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,
+                       PRESENT =>      \&my_present_handler,
+                       SCAN    =>      \&my_scan_handler,
+                       FETCH   =>      \&my_fetch_handler);
 
 After the custom event handles are declared, the server is launched
 by means of the method
 
-  $handle->launch_server("MyServer.pl", @ARGV);
+  $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
@@ -217,9 +234,15 @@ The argument hash passed to the init handler has the form
   $args = {
                                    ## Response parameters:
 
-            IMP_NAME  =>  ""       ## Z39.50 Implementation name
-            IMP_VER   =>  ""       ## Z39.50 Implementation version
-            ERR_CODE  =>  0        ## Error code, cnf. Z39.50 manual
+            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
             HANDLE    =>  undef    ## Handler of Perl data structure
          };
 
@@ -232,13 +255,14 @@ result sets or a handle to a back-end search engine of some sort),
 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
 
@@ -248,17 +272,18 @@ mous hash. The structure is the following:
   $args = {
                                    ## Request parameters:
 
-            HANDLE    =>  ref      ## Your session reference.
-            SETNAME   =>  "id"     ## ID of the result set
-            REPL_SET  =>  0        ## Replace set if already existing?
-            DATABASES =>  ["xxx"]  ## Reference to a list of data-
+            HANDLE    =>  ref,     ## Your session reference.
+            SETNAME   =>  "id",    ## ID of the result set
+            REPL_SET  =>  0,       ## Replace set if already existing?
+            DATABASES =>  ["xxx"], ## Reference to a list of data-
                                    ## bases to search
-            QUERY     =>  "query"  ## The query expression
+            QUERY     =>  "query", ## The query expression
+            RPN       =>  $obj,    ## Reference to a Net::Z3950::APDU::Query
 
                                    ## Response parameters:
 
-            ERR_CODE  =>  0        ## Error code (0=Succesful search)
-            ERR_STR   =>  ""       ## Error string
+            ERR_CODE  =>  0,       ## Error code (0=Succesful search)
+            ERR_STR   =>  "",      ## Error string
             HITS      =>  0        ## Number of matches
          };
 
@@ -298,6 +323,174 @@ it is perfectly legal to not accept boolean operators, but you SHOULD
 try to return good error codes if you run into something you can't or
 won't support.
 
+A more convenient alternative to the QUERY member may be the RPN
+member, which is a reference to a Net::Z3950::APDU::Query object
+representing the RPN query tree.  The structure of that object is
+supposed to be self-documenting, but here's a brief summary of what
+you get:
+
+=over 4
+
+=item *
+
+C<Net::Z3950::APDU::Query> is a hash with two fields:
+
+Z<>
+
+=over 4
+
+=item C<attributeSet>
+
+Optional.  If present, it is a reference to a
+C<Net::Z3950::APDU::OID>.  This is a string of dot-separated integers
+representing the OID of the query's top-level attribute set.
+
+=item C<query>
+
+Mandatory: a refererence to the RPN tree itself.
+
+=back
+
+=item *
+
+Each node of the tree is an object of one of the following types:
+
+Z<>
+
+=over 4
+
+=item C<Net::Z3950::RPN::And>
+
+=item C<Net::Z3950::RPN::Or>
+
+=item C<Net::Z3950::RPN::AndNot>
+
+These three classes are all arrays of two elements, each of which is a
+node of one of the above types.
+
+=item C<Net::Z3950::RPN::Term>
+
+See below for details.
+
+=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 soon.)
+
+=back
+
+=over 4
+
+=item *
+
+C<Net::Z3950::RPN::Term> is a hash with two fields:
+
+Z<>
+
+=over 4
+
+=item C<term>
+
+A string containing the search term itself.
+
+=item C<attributes>
+
+A reference to a C<Net::Z3950::RPN::Attributes> object.
+
+=back
+
+=item *
+
+C<Net::Z3950::RPN::Attributes> is an array of references to
+C<Net::Z3950::RPN::Attribute> objects.  (Note the plural/singular
+distinction.)
+
+=item *
+
+C<Net::Z3950::RPN::Attribute> is a hash with three elements:
+
+Z<>
+
+=over 4
+
+=item C<attributeSet>
+
+Optional.  If present, it is dot-separated OID string, as above.
+
+=item C<attributeType>
+
+An integer indicating the type of the attribute - for example, under
+the BIB-1 attribute set, type 1 indicates a ``use'' attribute, type 2
+a ``relation'' attribute, etc.
+
+=item C<attributeValue>
+
+An integer 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
+indicates a relevance match.
+
+=back
+
+=back
+
+Note that, at the moment, 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
+
+       { use Data::Dumper; print Dumper($args->{RPN}) }
+
+you get something fairly human-readable.  But of course, the type
+distinction between the three different kinds of boolean node is
+important.
+
+By adding your own methods to these classes (building what I call
+``augmented classes''), you can easily build code that walks the tree
+of the incoming RPN.  Take a look at C<samples/render-search.pl> for a
+sample implementation of such an augmented classes technique.
+
+
+=head2 Present handler
+
+The presence of a present handler in a SimpleServer front-end is optional.
+Each time a client wishes to retrieve records, the present service is
+called. The present service allows the origin to request a certain number
+of records retrieved from a given result set.
+When the present handler is called, the front-end server should prepare a
+result set for fetching. In practice, this means to get access to the
+data from the backend database and store the data in a temporary fashion
+for fast and efficient fetching. The present handler does *not* fetch
+anything. This task is taken care of by the fetch handler, which will be
+called the correct number of times by the YAZ library. More about this
+below.
+If no present handler is implemented in the front-end, the YAZ toolkit
+will take care of a minimum of preparations itself. This default present
+handler is sufficient in many situations, where only a small amount of
+records are expected to be retrieved. If on the other hand, large result
+sets are likely to occur, the implementation of a reasonable present
+handler can gain performance significantly.
+
+The informations exchanged between client and present handle are:
+
+  $args = {
+                                   ## Client/server request:
+
+            HANDLE    =>  ref,     ## Reference to datastructure
+            SETNAME   =>  "id",    ## Result set ID
+            START     =>  xxx,     ## Start position
+            COMP      =>  "",      ## Desired record composition
+            NUMBER    =>  yyy,     ## Number of requested records
+
+
+                                   ## Respons parameters:
+
+            HITS      =>  zzz,     ## Number of returned records
+            ERR_CODE  =>  0,       ## Error code
+            ERR_STR   =>  ""       ## Error message
+          };
+
+
 =head2 Fetch handler
 
 The fetch handler is asked to retrieve a SINGLE record from a given
@@ -312,7 +505,7 @@ The parameters exchanged between the server and the fetch handler are
             HANDLE    =>  ref      ## Reference to data structure
             SETNAME   =>  "id"     ## ID of the requested result set
             OFFSET    =>  nnn      ## Record offset number
-            REQ_FORM  =>  "USMARC" ## Client requested record format
+            REQ_FORM  =>  "n.m.k.l"## Client requested format OID
             COMP      =>  "xyz"    ## Formatting instructions
 
                                    ## Handler response:
@@ -323,7 +516,7 @@ The parameters exchanged between the server and the fetch handler are
             ERR_CODE  =>  0        ## Error code
             ERR_STR   =>  ""       ## Error string
             SUR_FLAG  =>  0        ## Surrogate diagnostic flag
-            REP_FORM  =>  "USMARC" ## Provided record format
+            REP_FORM  =>  "n.m.k.l"## Provided format OID
          };
 
 The REP_FORM value has by default the REQ_FORM value but can be set to
@@ -335,16 +528,68 @@ indicate whether the error condition pertains to the record currently
 being retrieved, or whether it pertains to the operation as a whole
 (eg. the client has specified a result set which does not exist.)
 
-Record formats are currently carried as strings (eg. USMARC, TEXT_XML,
-SUTRS), but this will probably change to proper OID strings in the
-future (not to worry, though, the module will supply constant values
-for the common OIDs). If you need to return USMARC records, you might
-want to have a look at the MARC module on CPAN, if you don't already
-have a way of generating these.
+If you need to return USMARC records, you might want to have a look at
+the MARC module on CPAN, if you don't already have a way of generating
+these.
 
 NOTE: The record offset is 1-indexed - 1 is the offset of the first
 record in the set.
 
+=head2 Scan handler
+
+A full featured Z39.50 server should support scan (or in some literature
+browse). The client specifies a starting term of the scan, and the server
+should return an ordered list of specified length consisting of terms
+actually occurring in the data base. Each of these terms should be close
+to or equal to the term originally specified. The quality of scan compared
+to simple search is a guarantee of hits. It is simply like browsing through
+an index of a book, you always find something! The parameters exchanged are
+
+  $args = {
+                                               ## Client request
+
+               HANDLE          => $ref         ## Reference to data structure
+               TERM            => 'start',     ## The start term
+               NUMBER          => xx,          ## Number of requested terms
+               POS             => yy,          ## Position of starting point
+                                               ## within returned list
+               STEP            => 0,           ## Step size
+
+                                               ## Server response
+
+               ERR_CODE        => 0,           ## Error code
+               ERR_STR         => '',          ## Diagnostic message
+               NUMBER          => zz,          ## Number of returned terms
+               STATUS          => $status,     ## ScanSuccess/ScanFailure
+               ENTRIES         => $entries     ## Referenced list of terms
+       };
+
+where the term list is returned by reference in the scalar $entries, which
+should point at a data structure of this kind,
+
+  my $entries = [
+                       {       TERM            => 'energy',
+                               OCCURRENCE      => 5            },
+
+                       {       TERM            => 'energy density',
+                               OCCURRENCE      => 6,           },
+
+                       {       TERM            => 'energy flow',
+                               OCCURRENCE      => 3            },
+
+                               ...
+
+                               ...
+       ];
+
+The $status flag should be assigned one of two values:
+
+  Net::Z3950::SimpleServer::ScanSuccess  On success (default)
+  Net::Z3950::SimpleServer::ScanPartial  Less terms returned than requested
+
+The STEP member contains the requested number of entries in the term-list
+between two adjacent entries in the response.
+
 =head2 Close handler
 
 The argument hash recieved by the close handler has one element only:
@@ -361,15 +606,12 @@ or something similar, this is the place to do it.
 =head1 AUTHORS
 
 Anders Sønderberg (sondberg@indexdata.dk) and Sebastian Hammer
-(quinn@indexdata.dk).
+(quinn@indexdata.dk). Substantial contributions made by Mike Taylor
+(mike@miketaylor.org.uk).
 
 =head1 SEE ALSO
 
-perl(1).
-
 Any Perl module which is useful for accessing the database of your
 choice.
 
 =cut
-
-