TOC 
SpaceKit Documentation SeriesF. Morton
 Invisible Worlds, Inc.
 May 1, 2000

Blocks SpaceKit for Java

Abstract

The Blocks SpaceKit for Java is a software development kit that implements the Blocks eXtensible eXchange Protocol (BXXP) in Java [1]. The SpaceKit provides tools to access existing Blocks servers as well as providing a framework for developing Blocks servers using Java.

To subscribe to the Blocks discussion list, send e-mail; there is also a developers' site.



 TOC 

Table of Contents




 TOC 

1. What Do I Need To Use The Blocks SpaceKit for Java?

The Blocks SpaceKit for Java requires "spacekit.jar" to be in the $CLASSPATH as required by the Java JDK. Optionally, it can be included in the java command line using -classpath as in this example to ping the indicated blocks server:

java -classpath ./spacekit.jar SpacePing sqa.invisible.net

Java version 1.2.1 or later is required.

1.1 Additional Documentation



 TOC 

2. The Blocks SpaceKit for Java Client

2.1 A Complete SpaceExample For The Impatient

The SpaceExample shows a typical flow for creating a connection to a SpaceServer, creating a SpaceChannel and doing a simple SEP request:

public class SpaceExample {

//------------------------------------------------------------------------------
//  main
//------------------------------------------------------------------------------
public static void main(String[] argv) {
     SpaceServer server = null;

     try {
          //--------------------------------------------------------------------
          //  set blocks server host
          //--------------------------------------------------------------------
          if(argv.length != 1) {
               System.err.println("Usage: java SpaceExample hostname");
               return;
               }

          //--------------------------------------------------------------------
          //  instantiate the blocks server
          //--------------------------------------------------------------------
          server = new SpaceServer(argv[0]);

          server.setTrace(true);

          //--------------------------------------------------------------------
          //  connect to blocks server
          //--------------------------------------------------------------------
          SpaceResponse greeting = server.connect(true);

          //--------------------------------------------------------------------
          //  show valid profiles
          //--------------------------------------------------------------------
          String[] profiles = greeting.getProfiles();
          for(int k=0;k<profiles.length;++k) {
               System.err.println("Profile: " + profiles[k]);
               } 

          //--------------------------------------------------------------------
          //  show localization
          //--------------------------------------------------------------------
          System.err.println("Localize: " + greeting.getLocalize());

          //--------------------------------------------------------------------
          //  show features
          //--------------------------------------------------------------------
          System.err.println("Features: " + greeting.getFeatures());

          //--------------------------------------------------------------------
          //  free the greeting
          //--------------------------------------------------------------------
          server.free(greeting);

          //--------------------------------------------------------------------
          //  start a non-authenticated channel
          //--------------------------------------------------------------------
          SpaceChannel channel = server.getChannel(SpaceProfile.sep);
          SpaceResponse response = channel.start(false);

          //--------------------------------------------------------------------
          //  build an sep fetch request while waiting for start response
          //--------------------------------------------------------------------
          StringBuffer fetch = new StringBuffer();

          fetch.append("<fetch>\r\n");
          fetch.append("<union>\r\n");
          fetch.append("<intersect>\r\n");
          fetch.append("<compare subtree='doc.rfc' operator='contains' ");
          fetch.append("caseSensitive='false'>\r\n");
          fetch.append("<path><element property='email' /></path>\r\n");
          fetch.append("<value>mrose@</value>\r\n");
          fetch.append("</compare>\r\n");
          fetch.append("</intersect>\r\n");
          fetch.append("</union>\r\n");
          fetch.append("</fetch>\r\n");

          //--------------------------------------------------------------------
          //  wait on the channel start and see if error
          //--------------------------------------------------------------------
          channel.wait(response);

          if(response.isError()) {
               System.err.println("Start Error: " + response.getErrorCode());
               return;
               }

          //--------------------------------------------------------------------
          //  do the request
          //--------------------------------------------------------------------
          response = channel.request(fetch.toString(),true);

          if(response.isError()) {
               System.err.println("Request Error: " + response.getErrorCode());
               return;
               }

          //--------------------------------------------------------------------
          //  show the response on standard output
          //--------------------------------------------------------------------
          System.out.println(response.toString());

          //--------------------------------------------------------------------
          //  free the response
          //--------------------------------------------------------------------
          channel.free(response);
          }
     catch(SpaceTimeout e) {
          System.err.println(e.toString());
          }
     catch(SpaceException e) {
          System.err.println(e.toString());
          }
     finally {
          //--------------------------------------------------------------------
          //  always close the server
          //--------------------------------------------------------------------
          if(server != null) {
               server.close();
               }
          }
     }
}

Sample Output From SpaceExample:

------------------------------------------------------------------
:  ID  : Channel : Serial :  Length  : Frames : Complete : Error :
------------------------------------------------------------------
: 0    : 0       : 0      :      255 : 1      : Yes      : No    :
------------------------------------------------------------------
------------------------------------------------------------------
:  ID  : Channel : Serial :  Length  : Frames : Complete : Error :
------------------------------------------------------------------
: 0    : 0       : 0      :      255 : 1      : Yes      : No    :
------------------------------------------------------------------
Profile: http://xml.resource.org/profiles/sasl/ANONYMOUS
Profile: http://xml.resource.org/profiles/SEP
Profile: http://xml.resource.org/profiles/NULL/ECHO
Profile: http://xml.resource.org/profiles/NULL/SINK
Profile: http://xml.resource.org/profiles/sasl/OTP
Localize: en-US
Features: none

------------------------------------------------------------------
:  ID  : Channel : Serial :  Length  : Frames : Complete : Error :
------------------------------------------------------------------
: 0    : 0       : 1      :       51 : 1      : Yes      : No    :
------------------------------------------------------------------
------------------------------------------------------------------
:  ID  : Channel : Serial :  Length  : Frames : Complete : Error :
------------------------------------------------------------------
: 0    : 0       : 1      :       51 : 1      : Yes      : No    :
------------------------------------------------------------------
------------------------------------------------------------------
:  ID  : Channel : Serial :  Length  : Frames : Complete : Error :
------------------------------------------------------------------
: 0    : 1       : 1      :     1335 : 1      : Yes      : No    :
------------------------------------------------------------------
------------------------------------------------------------------
:  ID  : Channel : Serial :  Length  : Frames : Complete : Error :
------------------------------------------------------------------
: 0    : 1       : 1      :     1335 : 1      : Yes      : No    :
------------------------------------------------------------------
<response reqno='1' ttl='86400' creator="bxxp://sqa.invisible.net/">
<answers actualNum='1'>
<rfc name="doc.rfc.2629" serial="2">
<rfc.props relativeSize="46132" number="2629" category="info"></rfc.props>
<doc.props>
<doc.front>
<doc.title>Writing I-Ds and RFCs using XML</doc.title>
<doc.author initials="M.T." surname="Rose" fullname="Marshall T. Rose">
<organization>Invisible Worlds, Inc.</organization>
<address>
<postal>
<street>660 York Street</street>
<city>San Francisco</city>
<region>CA</region>
<code>94110</code>
<country>US</country></postal>
<phone>+1 415 695 3975</phone>
<email>mrose@invisible.net</email>
<uri>http://invisible.net/</uri></address></doc.author>
<doc.date month="June" year="1999"></doc.date>
<doc.area>General</doc.area>
<doc.keyword>RFC</doc.keyword>
<doc.keyword>Request for Comments</doc.keyword>
<doc.keyword>I-D</doc.keyword>
<doc.keyword>Internet-Draft</doc.keyword>
<doc.keyword>XML</doc.keyword>
<doc.keyword>Extensible Markup Language</doc.keyword></doc.front><doc.extras abstract="true" note="false"></doc.extras>
<doc.links>
</doc.links>
</doc.props>
<remote.props uri="http://sqa.invisible.net/public/rfc/html/rfc2629.html"></remote.props>
<remote.props uri="http://sqa.invisible.net/public/rfc/txt/rfc2629.txt" language="text"></remote.props>
</rfc>
</answers>
</response>
------------------------------------------------------------------
:  ID  : Channel : Serial :  Length  : Frames : Complete : Error :
------------------------------------------------------------------
: 0    : 0       : 1      :        0 : 1      : Yes      : No    :
------------------------------------------------------------------

2.2 Connecting

A single instance of the SpaceServer class exists for each socket connection to the space server. To open a connection to the server sqa.invisible.net on the default port (port 10288):

SpaceServer server = new SpaceServer("sqa.invisible.net");
SpaceResponse greeting = server.connect();

The connect() waits for the connection to complete, returning the greeting. To get a list of available profiles, use the getProfiles() method:

String[] profiles = greeting.getProfiles();
for(int k=0;k<profiles.length;++k) System.out.println(profiles[k]);

The profile array generally contains the following profiles:

Profile: http://xml.resource.org/profiles/sasl/ANONYMOUS
Profile: http://xml.resource.org/profiles/SEP
Profile: http://xml.resource.org/profiles/NULL/ECHO
Profile: http://xml.resource.org/profiles/NULL/SINK
Profile: http://xml.resource.org/profiles/sasl/OTP

Localization and features are accessible via the getLocalize() and getFeatures() methods, respectively.

Establishing the connection can throw both a SpaceException and a SpaceTimeout.

2.2.1 Connecting With Localization

When establishing the connection, an optional localization can be specified. The specification may include multiple languages. The order within the localize list is in priority order, left to right. If the server is unable to return messages in any of these languages, it defaults to "i-default."

The following is an example of connecting with three preferred languages, in priority order:

SpaceResponse greeting = server.connect("fr en en-us",true);

2.3 Channel Creation

Once a connection exists, a SpaceChannel must be created to the server to make subsequent requests. Two types of channels exist: Non-authenticated and authenticated channels.

2.3.1 Non-Authenticated Channels

A non-authenticated channel is created by passing a valid non-authenticating profile to getChannel():

SpaceChannel channel = server.getChannel(SpaceProfile.sep);

The permission level of a non-authenticated channel is determined by the server.

2.3.2 SASL Authenticated Channels

Two types of SASL authenticated channels exist: Anonymous and OTP (One Time Password).

2.3.2.1 SASL Anonymous Authenticated Channels

A SASL anonymous channel is created by passing a valid SpaceAuthenticator associated with the SASL anonymous profile:

SpaceAuthenticator authenticator = new SpaceAuthenticatorSasl(SpaceProfile.saslAnonymous);
authenticator.set("authenticator","yourusernamehere");
SpaceChannel channel = server.getChannel(authenticator);

2.3.2.2 SASL OTP (One-Time Password) Authenticated Channels

A SASL OTP autheticated channel is created by passing a valid SpaceAuthenticator associated with the SASL OTP profile:

SpaceAuthenticator authenticator = new SpaceAuthenticatorSasl(SpaceProfile.saslOtp);
authenticator.set("authenticator","yourusernamehere");
authenticator.set("passphrase","yourpassphrasehere");
SpaceChannel channel = server.getChannel(authenticator);

2.4 Requests

Once a valid channel is open, requests may be made to the space server using the Blocks Simple Exchange Profile (SEP). A typical "fetch" sequence looks like:

StringBuffer sep = new StringBuffer();
sep.append("<fetch>\r\n");
sep.append("<union>\r\n");
sep.append("<intersect>\r\n");
sep.append("<compare subtree='doc.edgar' operator='contains' caseSensitive='false'>");
sep.append("<path><element property='state.of.incorporation' /></path>\r\n");
sep.append("<value>IN</value>\r\n");
sep.append("</compare>\r\n");
sep.append("</intersect>\r\n");
sep.append("</union>\r\n");
sep.append("</fetch>\r\n");

SpaceResponse response = channel.request(sep.toString(),false);
//...can do other things here while fetch is processing
channel.wait(response);
//...use the response results using response.toString()
channel.free(response);

See "Responses and Flow Control" for an explanation of channel.wait().

2.5 XML Parsers

The response contains XML that may act as input to any XML parser. For example, the following creates a DOM document using the Apache-Xerces (xml.apache.org) parser:

InputStream in = response.getInputStream();
DOMParser parser = new DOMParser();
parser.parse(new InputSource(in));
DocumentImpl document = (DocumentImpl)parser.getDocument();

2.6 Responses and Flow Control

A SpaceResponse returns from many of the SpaceServer methods, such as connect(), start() and request(). All of these methods take an optional boolean "wait" argument. If set to true, the returned response is guaranteed to be "complete" unless an error has occurred. You can check to see if the response is an error using the "isError()" method. If "isError()" returns true, the methods "getErrorCode()" and "getErrorText()" returns the int error code and String error text, respectively.

If the optional "wait" argument is specified to be false, the requesting method returns immediately with an "incomplete" response while a separate thread continues to receive the response. Any number of incomplete requests can exist concurrently. At any time, whether a response is "complete" or "incomplete," you can check the state of the response using methods such as "isIncomplete()" and "getLength()." See the reference section for a complete list.

To wait for the response to complete, use the wait method from the SpaceServer:

channel.wait(response);

The wait() can be specified with an optional timeout. When wait() returns it is guaranteed to be complete unless an error occurs.

After the wait is complete, the response is removed from the response queue, but the content of the response is preserved, accessible with either "getString()" or "getInputStream()." To mark the content as no longer needed, close the response using:

channel.free(response);

2.7 Exceptions

Two exceptions are described in the Reference section: SpaceException and SpaceTimeout.

2.8 Tracing

Tracing is a very helpful debugging tool to view the response queue set by doing:

server.setTrace(true);

Since responses are often broken up into multiple frames, during tracing every time a frame arrives, the status of the entire response queue is shown to System.err, looking something like:

------------------------------------------------------------------
:  ID  : Channel : Serial :  Length  : Frames : Complete : Error :
------------------------------------------------------------------
: 0    : 7       : 23     :        0 : 0      : No       : No    :
: 1    : 7       : 22     :        0 : 0      : No       : No    :
: 2    : 7       : 21     :        0 : 0      : No       : No    :
: 3    : 7       : 20     :        0 : 0      : No       : No    :
: 4    : 7       : 19     :        0 : 0      : No       : No    :
: 5    : 7       : 18     :        0 : 0      : No       : No    :
: 6    : 7       : 17     :        0 : 0      : No       : No    :
: 7    : 7       : 16     :        0 : 0      : No       : No    :
: 8    : 7       : 15     :        0 : 0      : No       : No    :
: 9    : 5       : 9      :        0 : 0      : No       : No    :
: 10   : 7       : 14     :        0 : 0      : No       : No    :
: 11   : 3       : 8      :    27654 : 1      : Yes      : No    :
: 12   : 13      : 13     :        0 : 0      : No       : No    :
: 13   : 1       : 7      :    27654 : 4      : Yes      : No    :
: 14   : 11      : 12     :        0 : 0      : No       : No    :
: 15   : 0       : 6      :       51 : 1      : Yes      : No    :
: 16   : 9       : 11     :        0 : 0      : No       : No    :
: 17   : 0       : 5      :       51 : 1      : Yes      : No    :
------------------------------------------------------------------

Where:
ID=response queue number
Channel=channel number associated with the response
Serial=serial number associated with the response
Length=the number of bytes received so far
Frames=the number of frames received so far
Complete=is the response complete (Yes or No)
Error=has an error occurred (Yes or No)

2.9 Client Examples

2.9.1 SpaceAuthenticate

Description: Opens a channel with the SpaceProfile.saslAnonymous profile and the SpaceProfile.saslOtp profile.

Command Line Example:

java SpaceAuthenticate sqa.invisible.net blockmaster secretpassword

2.9.2 SpaceBig

Description: Implements the Blocks Information Groper.

Command Line Example (returns list of valid "name" attributes):

java SpaceBig -server sqa.invisible.net search "<union>...</union>"

Command Line Example (fetch the contents of the specified document to System.out):

java SpaceBig -server sqa.invisible.net fetch doc.rfc.2629

Command Line Example (store the block using content from System.in):

java SpaceBig -server sqa.invisible.net -username yourusername -passphrase yourpassphrase store test.yoursubtree test.yoursubtree.2629 write 1 <store.xml

Command Line Example (delete the specified block):

java SpaceBig -server sqa.invisible.net -username yourusername -passphrase yourpassphrase store test.yoursubtree test.yoursubtree.2629 delete 1

2.9.3 SpacePing

Description: Opens a channel with the SpaceProfile.nullEcho profile and passes the text "Ping" as a normal request, which is echoed back. Secondly, opens another channel with the SpaceProfile.nullSink profile.

Command Line Example:

java SpacePing sqa.invisible.net

2.9.4 SpaceTest

Description: Runs numerous tests, including using the Apache-Xerces parser.

Command Line Example:

java SpaceTest sqa.invisible.net

2.9.5 SpaceTest2

Description: Runs numerous tests.

Command Line Example:

java SpaceTest2 sqa.invisible.net


 TOC 

3. The Blocks SpaceKit for Java Server: SpaceBxxd

3.1 Configuration

3.1.1 Configuration File Format

SpaceBxxd requires a configuration file in order to start.

The configuration file defines general server parameters as well as the available profiles and their associated parameters. A typical configuration file contains the following parameters and profiles:

  <?xml version="1.0"?>
 
  <!DOCTYPE config PUBLIC "-//Blocks//DTD BXXD CONFIG//EN"
            "http://xml.resource.org/blocks/bxxd/config.dtd">

  <config>
    <bxxd port="10288">
      <parameter name="homeDirectory"   value="/usr/local/bxxd" />
      <parameter name="moduleDirectory" value="modules" />
      <parameter name="banner"
                 value="sqa.invisible.net ready to go" />
      <parameter name="logFile"         value="logs/bxxd.log" />
      <parameter name="debugLevel"      value="none" />

      <profile uri="http://xml.resource.org/profiles/sasl/OTP"
               module="sasl-otp">
        <parameter name="authDirectory" value="auth/" />
      </profile>

      <profile uri="http://xml.resource.org/profiles/sasl/ANONYMOUS"
               module="sasl-anon">
        <parameter name="acl"           value='subtree "" privs "fetch"' />
      </profile>

      <profile uri="http://xml.resource.org/profiles/NULL/ECHO"
               module="null">
        <parameter name="mode"          value="echo" />
      </profile>

      <profile uri="http://xml.resource.org/profiles/NULL/SINK"
               module="null">
        <parameter name="mode"          value="sink" />
      </profile>
    </bxxd>
  </config>

This is a configuration file for the default port 10288.

3.1.2 Running SpaceBxxd from the Command Line

SpaceBxxd can run from the command line in "stand-alone" mode. Once started, it continues to run indefinitely. When a client connects, it is the exclusive connection to the server until the close of the session. This mode of running is typically used for testing purposes.

When running in stand-alone mode, the "$CLASSPATH" environment variable should contain all classes required by the server, including spacekit.jar and any additional modules.

This example starts the server on port 12345 using the configuration file in the current directory named bxxd.config:

java SpaceBxxd -standalone -portno 12345 bxxd.config

Without "-portno" the server uses the default port, 10288.

3.1.3 Running SpaceBxxd Under inetd

To run the server under the supervision of inetd, first add the following to /etc/services:

bxxp            10288/tcp

Secondly, add the following to /etc/inetd.conf (Note: this shows as being on two lines but should actually be one line):

bxxp stream tcp nowait bxxpuser /usr/java1.2/bin/java java -classpath 
/iw/SpaceKit/java/current/classes/spacekit.jar SpaceBxxd /usr/local/bxxd/etc/bxxd.config

Where:

  1. bxxpuser is the supervisory username of the server.
  2. /usr/java1.2/bin/java is the path to the java runtime executable.
  3. /iw/SpaceKit/java/current/classes/spacekit.jar is the full pathname to the SpaceKit spacekit.jar file.
  4. /usr/local/bxxd/etc/bxxd.config is the full pathname to the server configuration file.

Finally, restart inetd as super-user:

kill -HUP pid

Where "pid" is the process id of inetd.

3.1.4 User Properties File Format

A properties file defines users of the system. The location of the user properties file is found in the configuration file. Two elements, the "homeDirectory" and the "authDirectory" combine to form the directory name containing the properties file. The filename itself is a combination of the port number with the ".profiles" suffix.

For port 12345 the typical user file pathname is:

/usr/local/bxxd/auth/12345.properties

The user file contains any number of users.

An example entry for a user file is:

username.fmorton.name=Frank Morton
username.fmorton.authentication.mechanism=otp
username.fmorton.authentication.sequence=9911
username.fmorton.authentication.seed=rwcsqa0120696
username.fmorton.authentication.key=c055f88c0ca8c0ac
username.fmorton.authentication.privacy=none
username.fmorton.authentication.algorithm=sha1

To create an OTP key, see the SpaceKeyOtp utility.

3.2 A Complete Module Example For The Impatient

You can create your own modules by defining a new profile and implementing a new server module as well as the client using the new profile.

In the example, we create a module named "reverse" that receives a server request, reverses the order of the octets in the request, and responds back to the client with the reversed results.

3.2.1 Profile Definition

To access your new module, first define the new profile and its corresponding module in the configuration file for the port using the new profile:

      <profile uri="http://xml.resource.org/profiles/NULL/REVERSE"
               module="reverse">
      </profile>

If you are using the server in stand-alone mode, you will need to restart since the configuration file loads at start time

3.2.2 Server-side SpaceModuleREVERSE

A module extends the class "SpaceModuleThead" as in the SpaceModuleREVERSE example:

public class SpaceModuleREVERSE extends SpaceModuleThread {

//------------------------------------------------------------------------------
//  run
//------------------------------------------------------------------------------
public void run() {
     try {
          //---------------------------------------------------------------
          //  see if channel start
          //---------------------------------------------------------------
          if(isStart()) {
               start("");
               return;
               }

          //--------------------------------------------------------------------
          //  normal processing for the module
          //--------------------------------------------------------------------
          String reverse = (new StringBuffer(request.toString())).reverse().toString();

          //--------------------------------------------------------------------
          //  return a response
          //--------------------------------------------------------------------
          respond(reverse);

          //--------------------------------------------------------------------
          //  log the reverse response
          //--------------------------------------------------------------------
          log("reverse request complete");
          }
     catch(SpaceTimeout e) {
          //---------------------------------------------------------------
          //  timeout
          //---------------------------------------------------------------
          fatal(e);
          }
     catch(SpaceException e) {
          //---------------------------------------------------------------
          //  exception
          //---------------------------------------------------------------
          fatal(e);
          }
     }
}

A module has two main jobs:

  1. Start the channel.
  2. Process requests.

See "Module Structure" below and the reference section for a complete description of the SpaceModuleThread class.

3.2.3 Client-side SpaceReverse

The SpaceReverse application is an example of how to access the "reverse" module:

public class SpaceReverse {
 
//------------------------------------------------------------------------------
//  main
//------------------------------------------------------------------------------
public static void main(String[] argv) {
     SpaceServer server = null;

     try {
          //--------------------------------------------------------------------
          //  parse the command line
          //--------------------------------------------------------------------
          String host = null;
          int port = SpaceServer.defaultPort;
 
          for(int k=0;k<argv.length;++k) {
               //---------------------------------------------------------------
               //  -portno
               //---------------------------------------------------------------
               if(argv[k].equalsIgnoreCase("-portno")) {
                    port = InvisibleUtil.parseInt(argv[++k]);
                    continue;
                    }
 
               //---------------------------------------------------------------
               //  host
               //---------------------------------------------------------------
               host = argv[k];
               } 
 
          //--------------------------------------------------------------------
          //  instantiate the blocks server
          //--------------------------------------------------------------------
          server = new SpaceServer(host,port);

          //--------------------------------------------------------------------
          //  connect to blocks server
          //--------------------------------------------------------------------
          SpaceResponse greeting = server.connect(true);

          server.free(greeting);

          //--------------------------------------------------------------------
          //  start a reverse channel
          //--------------------------------------------------------------------
          SpaceChannel channel = server.getChannel("http://xml.resource.org/profiles/REVERSE");
          SpaceResponse reverseStart = channel.start(true);

          //--------------------------------------------------------------------
          //  make a reverse request
          //--------------------------------------------------------------------
          SpaceResponse reverseResponse = channel.request("sdlroW elbisivnI");

          System.out.println("Response: " + reverseResponse.toString());

          channel.free(reverseResponse);
          channel.free(reverseStart);
          }
     catch(NullPointerException e) {
          System.err.println("java SpaceReverse [-portno port] host");
          }
     catch(SpaceTimeout e) {
          System.err.println(e.toString());
          }
     catch(SpaceException e) {
          System.err.println(e.toString());
          }
     finally {
          //--------------------------------------------------------------------
          //  close the connection
          //--------------------------------------------------------------------
          if(server != null) {
               server.close();
               }
          }
     }
}

Sample output from SpaceReverse:

Response: 
>tseuqer/<Invisible Worlds
>'1'=onqer tseuqer<

3.3 Module Structure

All modules extend the class "SpaceModuleThread."

Since SpaceBxxp is a multi-threaded server, all modules must be thread safe.

Module names specified in the configuration file, such as "sasl-otp," convert to a class name made up of "SpaceModule" and the "module" capitalized with all hyphens removed. So, for the module "sasl-otp," "SpaceModuleSASLOTP" is the final class name. This is necessary to avoid class naming problems.

The following protected instance variables are defined in SpaceModuleThread and are fully accessible by modules extending the class:

See the reference for a full definition of each class.

To access the content of the request, a module uses "request.toString()."

3.4 Responses

Four types of responses are available to client requests:

  1. start
  2. response
  3. error
  4. fatal

3.4.1 The start Response

When any module receives a request, the module must first determine whether it is a start channel request. If it is, it should respond using the start method with content as defined by the module API. An empty response is commonly used to acknowledge the start of a channel:

if(isStart()) {
     start("");
     return;
     }

3.4.2 The response Response

A response() is the normal method to respond to a request.

String results = yourResultsMethod();
respond(results);

3.4.3 The error Response

Use the error() method to return error responses. To return a standard error response, such as error code 550:

     error(550);

To return your own error code and message use:

     error(1000,"Your Error Message");

See SpaceMessage in the reference section for a list of standard error messages.

The error() methods log the errors as described in Logging below.

3.4.4 The fatal Response

In the event that a SpaceException or SpaceTimeout is thrown in your module, at a minimum the fatal() method should be called when catching either exception:

catch(SpaceTimeout e) {
     //--------------------------------------------------------------------
     //  timeout
     //--------------------------------------------------------------------
     fatal(e);
     }
catch(SpaceException e) {
     //--------------------------------------------------------------------
     //  exception
     //--------------------------------------------------------------------
     fatal(e);
     }

The fatal() method logs the exception as described in Logging below.

The fatal() method may be called any time in your catch clause. It does not have to be the first or last call in the clause.

3.5 Logging

The log() methods cause a message to be appended to the log file specified for the port in the configuration file.

The first form of the log method implies a "SpaceLog.info" message level:

log("your log message");

The second form of the log method takes a message level and message as arguments:

log(SpaceLog.notify,"your notify message");

See SpaceLog in the reference section for a complete list of message levels.

The log contains the following items on each line:

Sample log messages from SpaceModuleREVERSE:

03/18 12:01:16 info    12345       listen
03/18 12:01:26 info    12345       connect
03/18 12:01:27 info    12345.0.1   module Reverse
03/18 12:01:27 info    12345.0.1   start
03/18 12:01:27 info    12345.1.1   module Reverse
03/18 12:01:27 info    12345.1.1   start
03/18 12:01:27 info    12345.1.1   reverse request complete
03/18 12:01:27 info    12345       disconnect

The "module Reverse" message shows two times because the first request on channel 0 is the start channel request. The second message is from the request itself on channel 1. Since serial numbers are reused, both requests used serial number 1 in this example.

3.6 Users

The user definition file for a particular port can be loaded using the loadUser() method. This loads all users for the port into a SpaceUserCache(). Note that subsequent calls to the method recognize that the cache is already loaded and therefore it will not be reloaded unnecessarily.

The method getUser() gets a specific element from the cache. For example, if the user file contains this entry for the user fmorton:

username.fmorton.authentication.sequence=9911

To get that value, pass the element class and name to the method:

String sequence = getUser("fmorton","authentication","sequence");

Define any additional classes and elements by inserting the text into the user definition file. For example, if the SpaceModuleREVERSE wanted to optionally capitalize the reversed response for certain users, add an element:

username.fmorton.reverseattribute.capitalize=yes

Then to get the value of the capitalize element in the module:

String capitalize = getUser("fmorton","reverseattribute","capitalize");

The user definition file can contain any number of elements in any order.

3.7 Saving State

Since multiple requests can be made on a channel, saving results from a previous request is often necessary. Every channel has an optional list of parameters that may be set. The parameters are key-value pairs.

Parameters may be set on channel zero, the start channel and the current channel using:

  1. setOnChannelZero()
  2. setOnStartChannel()
  3. set()

As an example, the SpaceModuleSASLOTP saves the authenticator as a channel parameter during channel start in both the start channel and channel zero:

setOnChannelZero("authenticator",authenticator);
setOnStartChannel("authenticator",authenticator);

Where the variable "authenticator" contains, as an example, the value "fmorton."

To set a parameter on the current channel:

set("thingtoremenber",thingtoremember);

To retrieve a value, use the following access methods:

  1. getFromChannelZero()
  2. getFromStartChannel()
  3. get()

To retrieve the authenticator from channel zero set by SpaceModuleSASLOTP:

String authenticator = getFromChannelZero("authenticator");

Any number of parameters by any name may be set.



 TOC 

4. Localization Class Structure

Localization support can be added to the SpaceKit by creating a message class for the particular language to support. The class name is "SpaceMessage" followed by the language mnemonic with hyphens removed and capitalized. For example, the class name for "en-us" is "SpaceMessageENUS" and is an implemention of the "SpaceMessage" interface.

The class contains one method get() with a single int argument specifying the message number. Error codes are considered message numbers.

The class must be defined in the $CLASSPATH for the Java virtual machine.

4.1 A Localization Class Example

This is the implementation of the "en-us" language as distributed with the Blocks SpaceKit for Java:

public class SpaceMessageENUS implements SpaceMessage {
 
//------------------------------------------------------------------------------
//  lookup message
//------------------------------------------------------------------------------
public String get(int messageCode) {
     if(messageCode == 421) return("service not available");
     if(messageCode == 450) return("requested action not taken");
     if(messageCode == 451) return("requested action aborted");
     if(messageCode == 454) return("temporary authentication failure");
     if(messageCode == 500) return("general syntax error");
     if(messageCode == 501) return("missing NUL separator");
     if(messageCode == 504) return("parameter not implemented");
     if(messageCode == 530) return("authentication required");
     if(messageCode == 534) return("authentication mechanism insufficient");
     if(messageCode == 535) return("authentication failure");
     if(messageCode == 537) return("action not authorized for user");
     if(messageCode == 538) return("authentication mechanism requires encryption");
     if(messageCode == 550) return("requested action not taken");
     if(messageCode == 553) return("parameter invalid");
     if(messageCode == 554) return("transaction failed");

     return(null);
     }
}


 TOC 

5. Utilities

5.1 SpaceKeyOtp: OTP Key Generation

Each user defined in a user properties file contains an authorization key associated with the authorization mechanism. The SpaceKeyOtp utility arguments are algorithm, passphrase (not included in the user properties file), seed and count:

java SpaceKeyOtp sha1 secretpassphrase rwcsqa0120696 9911

The key is sent to standard output:

c055f88c0ca8c0ac


 TOC 

6. Reference

6.1 SpaceAuthenticatorSasl

constructor SpaceAuthenticatorSasl(String profile)

Valid profiles include:
http://xml.resource.org/profiles/sasl/ANONYMOUS
http://xml.resource.org/profiles/SEP
http://xml.resource.org/profiles/NULL/ECHO
http://xml.resource.org/profiles/NULL/SINK
http://xml.resource.org/profiles/sasl/OTP

String getProfile()

Get the profile associated with the authenticator.

String getRequest()

Get the SEP request used to create the channel.

String getResponse(SpaceResponse response)

Get the necessary response to the response returned from getRequest().

String set(String key,String value)

Set a key-value pair used by getRequest() and getResponse().

6.2 SpaceChannel

void free(SpaceResponse response)

Free a response.

int getChannel()

Get the channel number.

boolean getError()

Get if channel error.

int getErrorCode()

Get the most recent error code.

String getErrorCodeString()

Get the most recent error code string.

String getErrorString()

Get the most recent error description.

String getMime()

Get the MIME type of the last response on the channel.

String getProfile()

Get the channel profile.

long getSeqno()

Get the channel seqno.

boolean getStarted()

Get if the channel has been started.

boolean isError()

Get if an error has occurred on the channel.

boolean isNotStarted()

Get if the channel has not been started.

boolean isStarted()

Get if the channel has been started.

SpaceResponse request(String request) throws SpaceTimeout,SpaceException

SpaceResponse request(String request,boolean wait) throws SpaceTimeout,SpaceException

Send a request with optional wait.

SpaceResponse respond(String request) throws SpaceTimeout,SpaceException

SpaceResponse respond(String request,boolean wait) throws SpaceTimeout,SpaceException

Send a response with optional wait. A "respond" is the same as a "request" without the "<request reqno=’n’>…</request>" tags. It is used to respond to a SASL OTP challenge.

6.3 SpaceConfig

constructor SpaceConfig()

String get(String key)

Get the configuration parameter using the supplied key.

String getGreeting()

Get the greeting response associated with the configuration.

SpaceProfile getProfile(String uri)

Get the profile associated with the supplied uri.

void set(String key,String value)

Set the configuration parameter using the supplied key-value pair.

6.4 SpaceException

constructor SpaceException()

constructor SpaceException(String message)

6.5 SpaceLog

static int error = 0

static int debug1 = 1

static int debug2 = 2

static int debug3 = 3

static int debug4 = 4

static int notify = 5

static int fatal = 6

static int info = 7

static int stats = 8

static int system = 9

static int user = 10

constructor SpaceLog()

constructor SpaceLog(SpaceConfig config,int port)

void println(String message)

Append a message to the log.

void println(int level,String message)

Append a message to the log.

void println(int level,String message,SpacePacket packet)

Append a message to the log. Note that a SpacePacket is the super-class of both SpaceRequest and SpaceResponse.

6.6 SpaceMessage

static String get(int errorCode)

Returns the textual version of the specified error code. Known codes are:
421 = "service not available"
450 = "requested action not taken"
451 = "requested action aborted"
454 = "temporary authentication failure"
500 = "general syntax error"
501 = "syntax error in parameters"
504 = "parameter not implemented"
530 = "authentication required"
534 = "authentication mechanism insufficient"
535 = "authentication failure"
537 = "action not authorized for user"
538 = "authentication mechanism requires encryption"
550 = "requested action not taken"
553 = "parameter invalid"
554 = "transaction failed"

6.7 SpaceModuleThread

void error(int errorCode)

Respond with an error and a standard error code.

void error(int errorCode,String errorString)

Respond with an error and a supplied error code and error string.

void fatal(Exception e)

Respond with a fatal error in a catch clause.

String get(String key)

Get a channel parameter value.

SpaceChannel getChannel()

Get the requesting channel.

String getFromChannelZero(String key)

Get a channel parameter value from channel zero.

String getFromStartChannel(String key)

Get a channel parameter value from the start channel.

int getSerial()

Get the requesting serial number.

SpaceConfig getConfig()

Get the module configuration.

String getUser(String authenticator,String name)

Get the specified user property value.

SpaceUserCache getUserCache()

Get the module user cache.

boolean isStart()

See if the request is a start channel request.

void loadUser()

Load the user properties for the port if not already loaded.

void log(String message)

Log a message with the SpaceLog.info level.

void log(int level,String message)

Log a message with the specified level.

void respond(String payload)

Respond with a normal supplied response.

void run()

The thread run() method. Overridden by the module.

void set(String key,String value)

Set a channel parameter value.

void setOnChannelZero(String key,String value)

Set a channel parameter value on channel zero.

void setOnStartChannel(String key,String value)

Set a channel parameter value on the start channel

void start(String payload)

Respond to a start channel request.

6.8 SpacePacket

constructor SpacePacket(SpaceChannel channel,int serial)

void free()

Free the packet by marking the content null.

String getChallengeString() throws SpaceException

Get the challenge text. First check if isChallenge() == true.

int getChannel()

Get the channel number from the channel associated with the packet.

boolean getError()

Get if an error has occurred on the channel associated with the packet.

int getErrorCode()

Get the most recent error code from the channel associated with the packet.

String getErrorCodeString()

Get the most recent error code string from the channel associated with the packet.

String getErrorString()

Get the most recent error description from the channel associated with the packet.

String getFeatures()

Get the features associated with the connection

int getFrameCount()

Get the current number of frames making up the packet.

InputStream getInputStream()

Get the input stream of the current content. Used as input for XML parsers.

String getLocalize()

Get the localization associated with the connection

String getMime()

Get the MIME type of the last packet on the channel associated with the packet.

String getNames()

Get the names from the root level of each block in the packet (must parse() first).

String getProfile()

Get the channel profile from the channel associated with the packet.

String[] getProfiles()

Get the profiles associated with the connection

long getSeqno()

Get the channel seqno from the channel associated with the packet.

int getSerial()

Get the serial associated with the packet.

boolean isChallenge() throws SpaceException

Is this response an authentication challenge?

boolean isComplete()

Is this packet complete?

boolean isError()

Has an error occurred or been found by parsing the response?

boolean isIncomplete()

Is this response incomplete?

int length()

Returns the length in bytes of the current packet content.

void parse() throws SpaceException

Parse the current response content for errors and challenges.

String toString()

Convert the entire packet content to a String.

6.9 SpaceProfile

static String sep = "http://xml.resource.org/profiles/SEP"

static String tls = "http://xml.resource.org/profiles/TLS"

static String saslAnonymous = "http://xml.resource.org/profiles/sasl/ANONYMOUS"

static String saslOtp = "http://xml.resource.org/profiles/sasl/OTP"

static String saslExternal = "http://xml.resource.org/profiles/sasl/EXTERNAL"

static String nullEcho = "http://xml.resource.org/profiles/NULL/ECHO"

static String nullSink = "http://xml.resource.org/profiles/NULL/SINK"

constructor SpaceProfile(String uriArg)

void set(String key,String value)

Set a profile parameter with the supplied key-value pair.

String get(String key)

Get a profile parameter value with the supplied key.

String getUri()

Get the URI associated with the profile.

6.10 SpaceResponse extends SpacePacket

constructor SpaceResponse(SpaceChannel channel,int serial)

SpaceResponseHeader getResponseHeader()

Get the header associated with the response.

6.11 SpaceRequest extends SpacePacket

constructor SpaceRequest(SpaceChannel channel,int serial)

SpaceRequestHeader getRequestHeader()

Get the header associated with the request.

6.12 SpaceSep

static String fetch(String subtree)

static String fetch(String subtree,String element)

Create a SEP fetch request with the subtree name and optional element.

static String lock(String subtree)

Create a SEP lock subtree request.

static String release(int prevno)

static String release(int prevno,String action)

Create a SEP release subtree request with prevno and optional action. Valid contents for action are commit (which is the default) and rollback.

static String store(String blockname,String action,String serial,String blockcontent)

Create a SEP store request. Valid contents for action are create, update, write or delete.

6.13 SpaceServer

constructor SpaceServer() throws SpaceException

void close()

Close the connection. This is best done in a finally clause within the same class as the connection creator.

SpaceResponse connect() throws SpaceTimeout,SpaceException

SpaceResponse connect(boolean wait) throws SpaceTimeout,SpaceException

SpaceResponse connect(String localize) throws SpaceTimeout,SpaceException

SpaceResponse connect(String localize,boolean wait) throws SpaceTimeout,SpaceException

Connect to the server as defined by the class host and port. The default port number is 10288.

void free(SpaceResponse response)

Free a response by removing it from the response queue.

String getHost()

Get the host name for the connection.

int getPort()

Get the port number for the connection.

SpaceResponse request(SpaceChannel channel,String request) throws SpaceTimeout,SpaceException

SpaceResponse request(SpaceChannel channel,String request,boolean wait) throws SpaceTimeout,SpaceException

Send a request on the specified channel with optional wait.

SpaceResponse respond(SpaceChannel channel,String request) throws SpaceTimeout,SpaceException

SpaceResponse respond(SpaceChannel channel,String request,boolean wait) throws SpaceTimeout,SpaceException

Send a response on the specified channel with optional wait. A "respond" is the same as a "request" without the "<request reqno=’n’>…</request>" tags. It is used to respond to a SASL OTP challenge.

void setHost(String value)

Set the host name for the connection prior to connect().

void setPort(int value)

Set the port for the connection prior to connect().

void setTimeout(int value) throws SpaceException

Set the socket read timeout value in milliseconds prior to connect. Default is 600000 (10 minutes).

void setTrace(boolean value)

Set if tracing should be output to System.err.

SpaceResponse start(SpaceChannel channel) throws SpaceTimeout,SpaceException

SpaceResponse start(SpaceChannel channel,boolean wait) throws SpaceTimeout,SpaceException

Start a channel on the specified channel with an optional wait.

void trace()

Output a trace frame to System.err.

SpaceResponse wait(SpaceResponse response) throws SpaceException

SpaceResponse wait(SpaceResponse response,long timeout) throws SpaceException

Wait for a response to be complete with optional timeout in milliseconds.

6.14 SpaceSocket

WARNING: The SpaceSocket class is for low-level access to the socket used by SpaceServer. This class should not be called directly when combined with the SpaceServer class. This is a utility class for writing your own driver.

static int defaultTimeout = 600000; //10 minutes

constructor SpaceSocket()

void close()

Close the socket.

void connect() throws SpaceException

Connect using the current settings.

boolean getDebug()

Get current debug setting.

String getHost()

Get host name for the socket.

int getPort()

Get the port number for the socket (default is SpaceSocket.defaultTimeout).

String read() throws SpaceTimeout,SpaceException

Read a line from the socket.

int read(SpaceResponse response,int size) throws SpaceTimeout,SpaceException

Read a specified number of bytes from the socket into the supplied response.

void setDebug(boolean value)

Set the debug setting.

void setHost(String value)

Set the host name prior to connect().

void setPort(int value)

Set the port number prior to connect().

void setTimeout(int value) throws SpaceException

Set the read timeout value for the socket (default is SpaceSocket.defaultTimeout).

void write(String string) throws SpaceException

Write the String to the socket.

6.15 SpaceTimeout

constructor SpaceTimeout()

constructor SpaceTimeout(String message)

6.16 SpaceUserCache

constructor SpaceUserCache()

String get(String name)

Get an element using the already set authenticator and class.

String get(String propertyClass,String name)

Get an element using the already set class.

String get(String authenticator,String propertyClass,String name)

Get the specified element.

void load(SpaceConfig config,SpaceProfile profile)

Load the user cache as specified by the configuration and profile.

void setAuthenticator(String value)

Set the authenticator used by the get() methods.

void setPropertyClass(String value)

Set the property class used by the get() methods.

void store(SpaceConfig config,SpaceProfile profile)

Store the user configuration to the pathname supplied in the configuration.


 TOC 

References

[1] Rose, M.T., "The Blocks eXtensible eXchange Protocol", draft-mrose-blocks-protocol-02 (work in progress), April 2000.
[2] Rose, M.T., "The Blocks Simple Exchange Profile", draft-mrose-blocks-exchange-01 (work in progress), April 2000.


 TOC 

Author's Address

  Frank Morton
  Invisible Worlds, Inc.
  1179 North McDowell Boulevard
  Petaluma, CA 94954-6559
  US
Phone:  +1 707 789 3700
EMail:  fmorton@invisible.net
URI:  http://invisible.net/


 TOC 

Appendix A. Blocks Public License

Blocks Public License

Copyright (c) 2000, Invisible Worlds, Inc.

All rights reserved.

Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL INVISIBLE WORLDS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.