|
SDLIP Java Transport Module |
SDLIP document map and recommended reading sequence (click to navigate):
This document provides a brief tutorial to writing Java implementations of SDLIP clients and servers, and contains the Java binding for the SDLIP interface. The document further contains complete code examples for both clients and collection wrappers (LSPs). It is assumed that the reader is familiar with the SDLIP-Core document.
Content:
1 Overview 2 Tutorial 2.1 SDLIP toolkit 2.2 Setting up the environment 2.3 SimpleClient 2.4 SimpleLSP 2.5 XMLObject 2.6 Toolkit Utilities 2.7 Useful example applications 3 Java Binding 4 References
For reference, Figure 1 recalls SDLIP's implementation architecture, which was introduced in the SDLIP-Core document.
Figure 1: This document covers client application and LSP construction
For the left side of Figure 1, this document explains how to write a client application. For the right side of the Figure, the following material explains how to write a Library Service Proxy (LSP). We also explain an 'SDLIP Toolkit' that provides useful facilities to make the creation of applications and LSPs easy. Among the toolkit's facilities are client and server transport modules for both DASL and CORBA transports. The reader does not need to understand the details of these transports. But this document explains how to instantiate and use the respective facilities. Recall that a single LSP may be made accessible through multiple transports, such as DASL and CORBA. We show how this is done.
The SDLIP Java binding contains three Java interfaces to correspond to the three main SDLIP interfaces:
These interfaces comprise methods to be implemented by LSPs and to be called by clients. Furthermore, there is an auxiliary interface which facilitates convenient implementations of XML data structures. The rationale for this interface is presented in Sec 2.5. Finally, is the implementation of the SDLIP exception conditions.LSP developers must implement at least the Search interface. This interface is sufficient for a realization of a stateless server. The optional Metadata interface allows SDLIP clients to discover the capabilities of SDLIP LSPs. It was designed to be very simple to encourage support from LSP developers.
Both client application and LSP builders would typically employ transport modules like the ones provided in the SDLIP distribution toolkit. The following tutorial gives an example of an SDLIP client that uses the toolkit. The client performs a query, and then lists the properties of all documents found. An example LSP is included as well. After these code examples, some of the toolkit utilities are introduced. Section 3, finally, details the Java interfaces that realize the SDLIP operations.
If you use Windows 95/NT type
set CLASSPATH=%CLASSPATH%;c:\your-dir\sdlip-1_x.jar;c:\your-dir\examples-sdlip-1_x.jar
On UNIX using tcsh type
setenv CLASSPATH "$CLASSPATH":/your-dir/sdlip-1_x.jar:/your-dir/examples-sdlip-1_x.jar
where your-dir is the directory in which the Java archives can be found. The file corba-sdlip-1_x.jar contains the SDLIP/CORBA binding and and a complete CORBA Object Request Broker (ORB). Include it in your CLASSPATH before the two archives above if you wish to run servers and clients using the SDLIP/CORBA transport modules. Notice that this provides you with effortless CORBA accessibility: Since the jar file contains the CORBA ORB, no additional CORBA installation or expertise is required.
A typical SDLIP client begins by creating a transport module. For this it uses a static method of the class ClientTransportModule (a parent of both ClientDaslTransport and ClientCorbaTransport). The address of an LSP is represented using a URI which is passed to that static method. This URI has to be made available by the LSP maintainers. More on that later in this section. Depending on the type of the URI, either an SDLIP/DASL or SDLIP/CORBA transport module instance is created and returned: If the LSP address is a URL (i.e. starts with "http://"), then a DASL transport is created. If instead the URI is a CORBA object identifier of the server transport module object (i.e. starts with "IOR:"), then a CORBA transport is created. Note that at compilation time the client developer does not have to worry about whether the SDLIP/DASL or SDLIP/CORBA transport module or both will be used later on in the client application. The corresponding transport modules are loaded dynamically at runtime.
The client invokes all subsequent SDLIP operations on that local client
transport module instance. Every transport module implements the Search,
ResultAccess
and Metadata interfaces. Methods invoked on the transport module
instance transparently access the corresponding SDLIP LSP via the server
transport module (as per Figure 1). Here is an example of a client sending
a search request to an SDLIP LSP:
import sdlip.helpers.*;
public static void main(String[] args)
{
|
Analogously on the server side, the initialization sequence of a typical LSP comprises the following steps:
import sdlip.helpers.*;
public static void main(String[] args)
{
|
The server developer has to make a decision, which transport mechanisms
(DASL, CORBA, or both) are to be supported by the LSP. This decision affects
only one or two lines of code of the LSP. It can be postponed until runtime
using the following "trick":
/* Initialize CORBA transport
only if runtime libraries are available */
try { ServerCorbaTransport t2 = new ServerCorbaTransport(); t2.addLSP("myLSP", lsp); } catch (NoClassDefFoundError err) {} |
As noted earlier, the LSP maintainer has to publish the address (URI) of the LSP, so that SDLIP clients can access it. Assume that "myhost.edu" is the DNS name of the machine on which the LSP runs in the above example, and that the transport module listens on port 8080. Then the SDLIP/DASL address of the LSP would be
http://myhost.edu:8080/myLSPIn the case of CORBA, name services are used to find an object identifier (IOR) from an object name. The CORBA server transport module that is included with the SDLIP distribution includes a simple name server that serves the IORs of the LSPs that are accessible through the transport module. This name server is accessible via HTTP, and by default listens on port 8181. Thus, assuming that the libraries for the SDLIP/CORBA binding are available at runtime (which is the case if corba-sdlip-1_x.jar is included in your CLASSPATH) , then pointing to the URL
http://myhost.edu:8181/myLSPusing your favorite browser would deliver the IOR of the CORBA object implementing the SDLIP/CORBA binding. Both the DASL URL http://myhost.edu:8080/myLSP, and the IOR returned for the CORBA object would be valid URIs for the initialization of the client transport. They refer to the same LSP.
To spare the client applications the step of resolving object references, ClientTransportModule also accepts a URI of the form sdlipns:http://myhost.edu:8181/myLSP and performs the necessary address resolution transparently. Thus, the LSP's URIs that the client application uses to create a client transport module could for our example be any of the following:
java sdlip.examples.SimpleClient http://coke.stanford.edu:8080/simplelsp "test query"
SimpleClient uses the transport modules provided as part of the standard SDLIP toolkit. The client prints out the list of URIs of the found documents and the total expected number of results.
The complete Java code of the client is listed below:
package sdlip.examples;
import sdlip.SDLIPException;
public class SimpleClient { public static void main(String args[]) { try {
/* an SDLIP/CORBA
or SDLIP/DASL transport module is created
/* the query
result will be held in a DOM-enabled sdlip.xml.dom.XMLObject
tm.search(12345,
/* some client SID */
/* get the
top XML element from an XMLObject */
/* iterate
over all found documents & print docIDs and doc properties*/
/*
print document ID */
/*
print all document properties in format NAME = "VALUE" */
} catch (SDLIPException sdlip) {
|
The LSP uses the SDLIP/DASL transport module. The transport module starts listening on the port specified on the command line. For example, the following command can be used to start the application on port 8080:
java sdlip.examples.SimpleLSP 8080
The complete Java code of the server is listed below:
package sdlip.examples;
import sdlip.SDLIPException;
public class SimpleLSP implements sdlip.Search, sdlip.Metadata { public void search(
/* Makes sure that the
XMLObject passed into query is
/* deliver the result;
in a real LSP this would access the underlying collection */
/* Methods of the source metadata interface */ public void getInterface(sdlip.XMLObject version) throws SDLIPException { version.setString("<SDLIPInterface
xmlns='http://interlib.org/SDLIP/1.0#'>" +
public void getSubcollectionInfo(sdlip.XMLObject subcolInfo) throws SDLIPException { subcolInfo.setString("<subcolInfo
xmlns='http://interlib.org/SDLIP/1.0#'>" +
public void getPropertyInfo(String subcolName, sdlip.XMLObject propInfo) throws SDLIPException { if(!"Dummy-Collection".equals(subcolName))
propInfo.setString("<propList
xmlns='http://interlib.org/SDLIP/1.0#'" +
public static void main(String args[]) throws Exception { /* Instantiate an SDLIP/DASL
transport module
|
Running SimpleClient against the SimpleLSP application produces the following output on the server side:
java sdlip.examples.SimpleLSP 8080
Query is:
<a:basicsearch xmlns:a="DAV:">
<a:select>
<a:allprop/>
</a:select>
<a:where>
<a:contains>test</a:contains>
</a:where>
</a:basicsearch>
The client's output is listed below:
java sdlip.examples.SimpleClient http://localhost:8080 "test query"
DOCUMENT: 1
http://purl.org/metadata/dublin_core#Title
= "Fighting a Wind Mill"
http://purl.org/metadata/dublin_core#Creator
= "Don Quijote"
DOCUMENT: 2
http://purl.org/metadata/dublin_core#Title
= "Document Without Creator"
EXPECTED TOTAL: 2
package sdlip.helpers;
public class SimpleXMLObject implements
sdlip.XMLObject {
public String getString()
{
|
For reasons of efficiency, the SDLIP toolkit provides an implementation of the XMLObject interface that uses the Document Object Model [DOM] representation to minimize the number of parsing processes. It offers a set of methods that allow the XML structure to be manpulated by using DOM interfaces. The class implementation is located in the package sdlip.xml.dom. The two essential new methods that it provides are:
public Element getElement();
and
public void setElement(Element e);
Thus, an XMLObject can be viewed as a tree of Elements, which may be accessed conveniently via appropriate methods. The complete documentation of the DOM-enabled XMLObject included in the SDLIP distribution can be found here. The toolkit utilities, which are described in the following section, include methods for extracting elements from XML elements.
Interface | Description |
Element base64Encode(Document d, byte[] b) | Binary data must be encoded in XML using some character-based encoding. For interoperability, SDLIP requires all binary data to be encoded using BASE64 standard. This method creates an element tagged with <base64> containing the BASE64-encoded binary data (the tag <base64> belongs to the SDLIP namespace). |
byte[] base64Decode(Element e) | Translated encoded binary data within <base64> tag into an array of bytes. |
Element createTextNode(Document d, String name, String value) | Creates an XML element tagged with the given name containing
a PCDATA element with the content given in value.
Example: createTextNode(d, "mytag", "some text") returns an XML structure <mytag>some text</mytag> |
Element getChild(Element parent, String tagName) | Returns the first child of the parent element having the given tagName. |
NodeList getChildElements(Element parent, String tagName) | Returns a list of all children of parent having the given tagName. |
String getChildText(Element parent, String tagName) | Delivers the textual content of the specified child element.
Shortcut for getText(getChild(parent, tagName)) |
Element getDescendant(Element e, String tagName) | Returns the first descendant of the given element in preorder traversal. |
Element getDescendantText(Element e, String tagName) | Delivers the textual content of the descendant.
Shortcut for getText(getDescendant(e, tagName)) |
String getText(Element e) | Returns the textual content of the given element.
Example: if e corresponds to <mytag>some text</mytag>, this method would deliver the string "some text". |
Note that the interface org.w3c.dom.Document is used in DOM as a factory for creating XML Elements. Therefore, it must be passed into all methods that create Elements. In DOM, Elements cannot be shared between different documents. If you intend to use the DOM-enabled implementation of XMLObject, you have to create all Elements using the Document instance delivered by the static method XMLObject.getDocument().
When created, a timer is initialized with a number of seconds until it expires. On expiry the timer calls a special method on the callback object passed to it during initialization. The callback object has to implement the single method
public void timeout(Object handle)of the interface sdlip.helpers.Timeoutable. The timer can be used in two scenarios:
Timer timer = new Timer(5, session);Once the timer expires, it calles the method session.timeout(null).
In the second scenario, some other object like the LSP itself implements the interface Timeoutable. A timer for the given session could be created using the constructor
Timer timer = new Timer(5, lsp, session);On expiry the timer calls the method lsp.timeout(session) and the LSP has to free the session's resources.
The timeout interval of a timer can be extended by X seconds using the method invocation
timer.setTimeout( timer.getTimeout() + X );For efficiency, all timer objects use a single thread. By default, the timers created are "robust". That means that once a timer expires, an extra thread is created and the timeout() method is called within this new thread. Thus, other timers are not affected. However, if the timeout() method returns immediately in a particular implementation, "regular" timers can be deployed for even more efficiency. A "regular" timer is created using the following constructor:
Timer timer = new Timer(5, lsp, session, Timer.REGULAR);
RangeEnumerator en = new RangeEnumerator("1,3-5,7-");The method next() must be called before any invocation of get(). An enumeration can be started from an arbitraty position, e.g. 4, using the call
while(en.next())
System.out.println("Next integer: " + en.get());
en.set(4);This method can also be used to verify whether the range contains some given integer X:
en.set(X);
if(en.next())
// X is within the range
DiscoverMetadata | Usage: java sdlip.examples.DiscoverMetadata <URL_or_IOR>
Fetches and prints out the complete metadata exposed by the LSP. |
Client | Usage: java sdlip.examples.Client <URL_or_IOR> <method> ...method
parameters...
Allows to invoke all methods defined in the Search and ResultAccess interfaces on an LSP using a command line tool. The <method> parameter corresponds to the name of the method to be invoked. Following methods are supported: search
<clientSID> <queryStr> <numDocs> <stateTimeoutReq>
|
SwitchBoardLSP | Usage: java sdlip.examples.SwitchBoardLSP <port>
Implements a stateful LSP for the Web site switchboard.com. This LSP runs on a specified port and accepts a DAV:basicsearch query containing a DAV:contains operator. The query must contain a last name of the person to search for and may optionally be preceded by the person's first name. For every query, all available properties are returned which include the full name, address and phone number. This LSP implements all currently defined SDLIP interfaces and methods. SwitchBoardLSP can be queried using the Client example application. It demonstrates the usage of RangeEnumerator and Timer utilities described in previous section. The LSP maintains state for maximum 10 minutes unless the extendStateTimeout method is called by the client. |
Recall that SDLIP operations of concern in the Java binding always occur within one address space: either between client application and client transport module, or between LSP and server transport module. Therefore, the Java binding makes two minor modifications to the Java bindings generated by an IDL compiler:
import org.omg.CORBA.IntHolder;
public interface XMLObject {
public String getString() throws SDLIPException;
public void setString( String XMLStr ) throws
SDLIPException;
}
public static final int RETURN_ALL_DOCS = -1;
public static final int MAINTAIN_UNLIMITED = -1;
public static final int UNKNOWABLE = -1;
public static final int NOT_YET_KNOWN = -2;
public void search(
int clientSID,
// Client-side session ID
XMLObject subcols,
// Choice of collections to search
XMLObject query,
// The query
int numDocs,
// Number of docs to return or RETURN_ALL_DOCS
XMLObject docPropList,
// Properties to return for each doc
int stateTimeoutReq,
// Number of seconds to maintain or MAINTAIN_UNLIMITED
XMLObject queryOptions,
// Additional info for the LSP
IntHolder expectedTotal,
// Expected number of results
// or UNKNOWABLE or NOT_YET_KNOWN
IntHolder stateTimeout,
// state timeout set at the server
IntHolder serverSID,
// Server-side session ID
XMLObject serverDelegate,
// Address for future service requests
XMLObject result )
throws SDLIPException ;
}
public interface ResultAccess
{
public static final int CANCEL_ALL_REQUESTS = 0;
public void getSessionInfo(
int serverSID,
IntHolder expectedTotal,
// or UNKNOWABLE or NOT_YET_KNOWN
IntHolder stateTimeout
)
throws SDLIPException ;
public void getDocs(
int serverSID,
int reqID,
XMLObject docPropList,
String docsToGet,
XMLObject res )
throws SDLIPException ;
public void extendStateTimeout(
int serverSID,
int additionalTime,
IntHolder timeAllotted )
throws SDLIPException ;
public void cancelRequest(
int serverSID,
int reqID )
throws SDLIPException ;
public void removeDocs(
int serverSID,
String docsToRemove )
throws SDLIPException ;
}
public void getVersion(
XMLObject version /* XML-encoded info about
the versions of each
supported
interface. */
)
throws SDLIPException;
public void getSubcollectionInfo(
/* XML list of subcollections
and their
supported
query languages */
XMLObject subcolInfo
)
throws SDLIPException;
public void getPropertyInfo(
/* {null} If not
supplied, request for default
subcollection. */
String subcolName,
/* Acceptable
properties of
specified subcollection, and whether they
are
searchable/retrievable */
XMLObject propInfo
)
throws SDLIPException;
public class SDLIPException extends Exception {
public static final short INVALID_REQUEST_EXC = 400;
public static final short UNAUTHORIZED_EXC = 401;
public static final short PAYMENT_REQUIRED_EXC = 402;
public static final short NOT_FOUND_EXC = 404;
public static final short ILLEGAL_METHOD_EXC = 405;
public static final short REQUEST_TIMEOUT_EXC = 408;
public static final short QUERY_LANGUAGE_UNKNOWN_EXC = 450;
public static final short BAD_QUERY_EXC = 451;
public static final short INVALID_PROPERTY_EXC = 452;
public static final short INVALID_SESSIONID_EXC = 453;
public static final short INVALID_SUBCOLLECTION_EXC = 454;
public static final short MALFORMED_XML_EXC = 455;
public static final short SERVER_ERROR_EXC = 500;
public static final short NOT_IMPLEMENTED_EXC = 501;
public static final short SERVICE_UNAVAILABLE_EXC = 503;
short code;
XMLObject details;
public SDLIPException(short code, String message) {
super(message);
this.code = code;
}
public SDLIPException(short code, String message, XMLObject
details) {
this(code, message);
this.details = details;
}
public short getCode() {
return code;
}
public String getDescription() {
return super.getMessage();
}
public XMLObject getDetails() {
return details;
}
}
[DOM] | DOM Working Group: Document Object Model (DOM) Level 1 Specification,
Version 1.0, W3C Recommendation 1 October, 1998
http://www.w3.org/TR/REC-DOM-Level-1/ |
[DublinCore] | Dublin Core Metadata Initiative: The Dublin Core: A Simple Content
Description Model for Electronic Resources, 1999
http://purl.org/dc/ |
[SDLIP-Core] | The Simple Digital Library Interoperability Protocol (SDLIP-Core),
1999
http://www-diglib.Stanford.EDU/~testbed/doc2/SDLIP/ |