$Id$
1. Introduction
2. The IFS Class
2.1. boolean search(IRQuery q) throws SearchException
2.2. int getFragmentCount()
2.3. InformationFragment getFragment(int i)
3. Configuration
4. Invocation
5. Example
Building a Z39.50 server with JZKit is very broadly similar to doing it in C using the Yaz toolkit's front-end server, or in Perl using the Net::Z3950::SimpleServer module. In all three cases, the bulk of the work is already done for you, and you essentially provide callbacks. The difference is that in JZKit, you do this by providing a class whose methods are the callbacks.
The class you must define is an Information Fragment Source, or IFS for short - that is, a class representing a set of records to be returned to the client. You must provide methods to populate the IFS with a set of records, to tell the generic server code how many there are, and to return them. The class itself must extend com.k_int.z3950.server.generic.GenericIFS.
Let's consider a small but real server, ``lineserver'', which serves lines from text files. Files accessible to the server are used as ``databases'', with each line considered to be a ``record''. Query terms are searched for with simple case-insensitive string matching, without regard for word boundaries. So for example, given a file containing the lines:
The Magician's Nephew The Lion, the Witch and the Wardrobe The Horse and his Boy Prince Caspian The Voyage of the Dawn Treader The Silver Chair The Last Battle
The following searches will find the specified number of records:
First we declare the IFS class itself:
package com.k_int.z3950.server.generic.line; import various stuff; public class LineIFS extends GenericIFS { private Vector lines = new Vector(); // of String // ...
It's up to you what fields (class variables) you want to include in your class. In this simple case, we just have a Vector of strings, each containing one line of the file that's specified in the search term.
Now we consider the three methods:
This is where the bulk of the work is done. This method is reponsible for understanding the IRQuery structure (q.v.), applying it to the database, whatever form that takes, and making the results available to the IFS object.
This is always the first of the three methods to be called.
Assuming it is successful, it returns a boolean indicating whether it has finished (true) or has merely started an asynchronous operation (false). Simple servers will nearly always return true. Failure is indicated by throwing a SearchException.
The SearchException object may be used to convey details error information back to the client, in the form of a BIB-1 diagnostic. Such diagnostics may be formed using the two-argument form of the SearchException constructor: the first argument is a string containing ``additional information'', and the second an Integer indicating the error condition - for example, error 235 may be used to indicate that a requested database does not exist, with the additional information specifying the name of the missing database:
throw new SearchException(dbName, new Integer(235));
The numeric codes, their interpretations, and the form of additional information that should accompany them are described in Appendix ERR.1 of the Z39.50-1995 standard document at lcweb.loc.gov/z3950/agency/defns/bib1diag.html
This must return the number of records that were found by the previously executed search() - in the case of lineserver, simply the size() of the lines Vector.
This must return the ith record of those found by the previously executed search(). Records are numbered from 1, so i will always be at least 1 and no more than result returned by getFragmentCount().
The returned record can be an object of any class that implements the InformationFragment interface. The simplest such class provided by JZKit is probably com.k_int.IR.Syntaxes.SUTRS which represents a record consisting of single human-readable string. Such records can be created and returned simply with:
return new SUTRS(myString);
In order to run your server, you'll need to make a properties file that specifies the name of your IFS class, so that the JZKit generic server can instantiate it to perform searches:
ifs=com.k_int.z3950.server.generic.line.LineIFS
You also need to specify the name of the class that implements the generic server, so that the low-level server code can find it:
evaluator=uk.org.miketaylor.jzserver.Searchable
You may also wish to specify which port it listens on, if you don't like the default of 2100:
port=3950
Finally, you will need to provide two more properties in your file, so that the system can tell how you want it to translate between different record syntaxes using XSL stylesheets:
XSLConverterConfiguratorClassName=com.k_int.IR.Syntaxes.Conversion.XMLConfigurator ConvertorConfigFile=/usr/local/src/z39.50/ki-all/ki-jzkit/etc/tests/test_server/SchemaMappings.xml
Once you have set up your properties file - lineserver.props, say - you can run com.k_int.z3950.server.ZServer with the name of your properties file as the only command-line argument:
java com.k_int.z3950.server.ZServer lineserver.props
And connect to it, search in it and retrieve records from it using the Z39.50 client of your choice. Here, for example, I am using the widely available YAZ command-line client:
$ yaz-client tcp:@:3950 Connecting...Ok. Z> find Makefile Number of hits: 14 Z> format xml Z> show 10+2 Records: 2 Record type: XML <?xml version="1.0" encoding="UTF-8"?> <sutrs>%.class: %.java</sutrs> Record type: XML <?xml version="1.0" encoding="UTF-8"?> <sutrs> javac $<</sutrs> Record type: XML Z>
You can see all this done in the com.k_int.z3950.server.generic.line package, which defines the LineIFS class discussed above.