Net-Z3950-SimpleServer
view release on metacpan or search on metacpan
SimpleServer.pm view on Meta::CPAN
The argument hash passed to the init handler has the form
$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
GROUP => "zzz" ## Under same conditions, this member
## contains the group in clear text
GHANDLE => $obj ## Global handle specified at creation
HANDLE => undef ## Handler of Perl data structure
};
The HANDLE member can be used to store any scalar value which will then
be provided as input to all subsequent calls (i.e. for searching, record
retrieval, etc.). A common use of the handle is to store a reference to
a hash which may then be used to store session-specific parameters.
If you have any session-specific information (such as a list of
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 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, with the code and the
associated additional information, ERR_STR returned.
=head2 Search handler
Similarly, the search handler is called with a reference to an
anonymous hash. The structure is the following:
$args = {
## Request parameters:
GHANDLE => $obj # Global handle specified at creation
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 databases to search
QUERY => "query", # The query expression as a PQF string
RPN => $obj, # Reference to a Net::Z3950::APDU::Query
CQL => $x, # A CQL query, if this is provided instead of Type-1
SRW_SORTKEYS => $x, # XXX to be described
PID => $x, # XXX to be described
PRESENT_NUMBER => $x, # XXX to be described
EXTRA_ARGS => $x, # XXX to be described
INPUTFACETS => $x, # Specification of facets required: see below.
## Response parameters:
ERR_CODE => 0, # Error code (0=Successful search)
ERR_STR => "", # Error string
HITS => 0, # Number of matches
ESTIMATED_HIT_COUNT => $x, # XXX to be described
EXTRA_RESPONSE_DATA => $x, # XXX to be described
OUTPUTFACETS => $x # Facets returned: see below.
};
Note that a search which finds 0 hits is considered successful in
Z39.50 terms - you should only set the ERR_CODE to a non-zero value
if there was a problem processing the request. The Z39.50 standard
provides a comprehensive list of standard diagnostic codes, and you
should use these whenever possible.
=head3 Query structures
In Z39.50, the most comment kind of query is the so-called Type-1
_query, a tree-structure of terms combined by operators, the terms
being qualified by lists of attributes.
The QUERY parameter presented this tree to the search function in the
Prefix Query Format (PQF) which is used in many applications based on
the YAZ toolkit. The full grammar is described in the YAZ manual.
The following are all examples of valid queries in the PQF.
dylan
"bob dylan"
@or "dylan" "zimmerman"
@set Result-1
@or @and bob dylan @set Result-1
@and @attr 1=1 "bob dylan" @attr 1=4 "slow train coming"
@attrset @attr 4=1 @attr 1=4 "self portrait"
You will need to write a recursive function or something similar to
parse incoming query expressions, and this is usually where a lot of
the work in writing a database-backend happens. Fortunately, you don't
need to support any more functionality than you want to. For instance,
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 is 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>
SimpleServer.pm view on Meta::CPAN
=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 or string indicating the value of the attribute - for example, under
BIB-1, if the attribute type is 1, then value 4 indicates a title
search and 7 indicates 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
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
{ 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.
Finally, when SimpleServer is invoked using SRU/SRW (and indeed using
Z39.50 if the unusual type-104 query is used), the query that is
_passed is expressed in CQL, the Contextual Query Language. In this
case, the query string is made available in the CQL argument.
=head3 Facets
Servers may support the provision of facets -- counted lists of field
values which may subsequently be be used as query terms to narrow the
search.
In SRU, facets may be requested by the C<facetLimit> parameter,
L<as documented in the OASIS standard that formalises the SRU specification|http://docs.oasis-open.org/search-ws/searchRetrieve/v1.0/os/part3-sru2.0/searchRetrieve-v1.0-os-part3-sru2.0.html#_Toc324162453>.
Its value is a string consisting of a comma-separated list of facet
specifications. Each facet specification consists of of a count, a
colon and a fieldname. For example, C<facetLimit=10:title,5:author>
asks for ten title facets and five author facets.
=head4 Request format
The facet request is passed to the search-handler function in the
INPUTFACETS parameter. Its value is rather complex, due to backwards
compatibility with Z39.50:
=over 4
=item *
The top-level value is a C<Net::Z3950::FacetList> array.
=item *
This is an array of C<Net::Z3950::FacetField> objects.
=item *
Each of these is an object with two members, C<attributes> and
C<terms>.
=item *
C<attributes> has type C<Net::Z3950::RPN::Attributes> and is a list of
objects of type C<Net::Z3950::RPN::Attribute>.
=item *
Each attribute has two elements, C<attributeType> and
C<attributeValue>. Each value is interpreted according to its
type. The meanings of the types are as follows:
=over 4
=item 1
The name of the field to provide values of the facets.
=item 2
The order in which to sort the values. (But it's not clear how this is
to be interpreted: it may be implementation dependent.)
=item 3
The number of facets to include for the specified field.
=item 4
The first facet to include in the response: for example, if this is
11, then the first ten facts should be skipped.
=back
=back
So for example, the SRU facet specification
C<facetLimit=10:title,5:author> would be translated as a
C<Net::Z3950::FacetList> list of two C<Net::Z3950::FacetField>s. The
C<attributes> of the first would be [1="title", 3=10], and those of
the second would be [1="author", 3=5].
It is not clear what the purpose of C<terms> is, but for the record,
this is how it is represented:
=over 4
=item *
C<terms> is a C<Net::Z3950::FacetTerms> array.
=item *
This is an array of C<Net::Z3950::FacetTerm> objects.
=item *
Each of these is an object with two members, C<term> and C<count>. The
first of these is an integer, the second a string.
=back
=head4 Response format
Having generated facets corresponding to the request, the search
handler should return them in the C<OUTPUTFACETS> argument. The
structure of this response is similar to that of the request:
=over 4
=item *
The top-level value is a C<Net::Z3950::FacetList> array.
=item *
This is an array of C<Net::Z3950::FacetField> objects.
=item *
Each of these is an object with two members, C<attributes> and
C<terms>.
=item *
C<attributes> has type C<Net::Z3950::RPN::Attributes> and is a list of
objects of type C<Net::Z3950::RPN::Attribute>.
=item *
Each attribute has two elements, C<attributeType> and
C<attributeValue>. Each value is interpreted according to its
type. The meanings of the types are as follows:
=over 4
=item 1
The name of the field for which terms are provided.
=back
(That is the only type used.)
=item *
C<terms> is a C<Net::Z3950::FacetTerms> array.
=item *
This is an array of C<Net::Z3950::FacetTerm> objects.
=item *
Each of these is an object with two members, C<term> and C<count>. The
first of these is a string containing one of the facet terms, and the
second is an integer indicating how many times it occurs in the
records that were found by the search.
=back
The example SimpleServer applicaation server C<ztest.pl> includes code
that shows how to examine the INPUTFACETS data structure and create
the OUTPUTFACETS structure.
=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 information exchanged between client and present handle is:
$args = {
## Client/server request:
GHANDLE => $obj ## Global handle specified at creation
HANDLE => ref, ## Reference to datastructure
SETNAME => "id", ## Result set ID
START => xxx, ## Start position
COMP => "", ## Desired record composition
SCHEMA_OID => "", ## Z39.50 schema (OID), if any
NUMBER => yyy, ## Number of requested records
## Response 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
result set (the front-end server will automatically call the fetch
handler as many times as required).
The parameters exchanged between the server and the fetch handler are:
$args = {
## Client/server request:
GHANDLE => $obj ## Global handle 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
( run in 1.643 second using v1.01-cache-2.11-cpan-39bf76dae61 )