NEESgrid logo

A walk through the nclient source code

Functional Outline

The goal of this code is to demonstrate the use and sequence of calling NTCP. Please do not consider it as production-quality code, and it certainly needs considerable work to make it less procedural. That being said, let's walk through it section by section.

The file you need to open is
$HOME/code/nclient/src/org/nees/ntcp/nclient/nclient.java

Header and Import

package org.nees.ntcp.nclient;
The package line defines what we call this code. Given that the source code is located in $HOME/code/nclient/src, files in the Java package 'org.nees.ntcp.client' can be found in
$HOME/code/nclient/src/org/nees/ntcp/nclient/
The next set of lines are the imports required to use NtcpHelper:
import org.nees.ntcp.ntcpServer.ParameterType; 
import org.nees.ntcp.ntcpServer.ControlPointType;
import org.nees.ntcp.ntcpServer.ControlPointParameterNameType;
import org.nees.ntcp.ntcpServer.ControlPointGeomParameterType;
import org.nees.ntcp.ntcpServer.GeomAxisType;
import org.nees.ntcp.ntcpServer.TransactionType;
import org.nees.ntcp.ntcpServer.TransactionStateType;
import org.nees.ntcp.server.util.NtcpHelper;
import org.nees.ntcp.ntcpServer.NtcpServer;

import java.math.BigInteger;
import java.lang.String;
import java.util.Vector;
import java.lang.Boolean;
import java.util.*;

You need these in any program calling NtcpHelper.

Class Definition and Variable Declarations

public class nclient {

public static void main (String args[]) {

String serverURL = "http://localhost:8090/";
String ogsaPath = "ogsa/services/nees/ntcp/";
String instanceName = "NTCPServer";
boolean beSecure = false;
NtcpServer Server = null;
String transName = "BlueFish";

Here, we have defined a class with the same name, nclient, with a main() function that we will invoke. In it, we define the machine running NTCP (serverURL), and the OGSA-specific path to NTCP (ogsaPath). Note that the NtcpHelper docs have a typo on that score - you do need the ogsaPath.

Next, we define the instanceName to be the default, "NTCPServer". This will always be the same.

Next, we disable security with 'beSecure = false'. Security works and is no longer the speed penalty it was; please see
http://neesgrid.org/documents/Release3.0/neespop/ch-ntcp.html for information on setting it up.

The transName string defines the base transaction name. These have to be unique, as you'll see later on in the code. A little further down, the 'memory' variable defines how long, in seconds, the NTCP server remembers transactions. Normally, you'd want NTCP to remember transactions for a long time; when debugging it would be useful for it to forget them so you can re-run the same client repeatedly. NTCP enforces this ordering:

propose timeout <= transaction timeout <= remembered until (memory)

        ControlPointGeomParameterType move = new ControlPointGeomParameterType();
Random ran = new Random();
BigInteger transNum = new BigInteger(16, ran);
ControlPointType ctrlPoint = new ControlPointType();
ControlPointType cpq = new ControlPointType();
TransactionStateType tstate;
int timeout = 120;
int memory = 128;
TransactionType trans = new TransactionType();
ControlPointType[] cpArray;
ParameterType[] ospArray;

Here we are defining the NTCP variables we will need later - the move itself, control point, geometry, transaction, and a random transaction number we will use to generate a unique transaction ID. The timeout is set to 2 minutes just to allow plenty of time to complete. Depending on network usage and CPU capacity, a quarter of this would normally suffice.
        // Real URL is container + ogsa name
serverURL += ogsaPath;
This constructs the full URL we will use to reach the NTCP server, which in this case is
http://localhost:8090/ogsa/services/nees/ntcp/
Note that the trailing slash is required; otherwise you'll get errors on openSession. You can see this path in the output from the container when it starts up:
startContainer:
[java] [03/15/2004 17:05:46:182 ] org.globus.ogsa.server.ServiceContainer [run:569] INFO: Starting SOAP server at: http://140.221.11.87:8090/ogsa/services/
[java] With the following persistent services:
[java]
[java] http://140.221.11.87:8090/ogsa/services/nees/ntcp/commandBufferService
[java] http://140.221.11.87:8090/ogsa/services/nees/ntcp/NTCPServer

Initialization

    // Name the control point we'll be using - ANCO
  ctrlPoint.setControlPointName("ANCO");

// Set up the move - displacement along X axis, 10mm
move.setName(ControlPointParameterNameType.displacement);
move.setAxis(GeomAxisType.x);
move.setValue(new Float(10.0));

// set the type (geom info into CP)
ctrlPoint.setControlPointType(new ControlPointGeomParameterType[] {move});

// Create an army of one. Or is that array?
cpArray = new ControlPointType[] {ctrlPoint};

// Create parameter for opensession
ospArray = new ParameterType[1];
ospArray[0] = NtcpHelper.getParameter("OneFish", "TwoFish");

First off, we name the control point we'll be, you guessed it, controlling. In this case, I called it ANCO, which is the brand of shake table I have at MCS. This name is defined in the LabVIEW code that drives the table. This has to match whatever your control system has.

Next, we define the move we're going to request - in this case, a single move to +10.0 on the X axis. Note that units are not part of the NTCP spec, so you are responsible for knowing what '10.0' means. In this case, the table expects millimeters.

A brief digression into ControlPoints, axes and geometry

Note that we take the move definition, and pack it into an array of ControlPointGeomParameterType. While seemingly a bit odd, this is part of the NTCP logic. Control points can have more than one axis, so you could ask ControlPoint 'foo' to move +X 5, rotate Z 20 degrees, and Y -30, all in a single request. To accomplish that, your control point array has one move per axis. In this case, we keep it simple and just move one axis.

Similarly, a proposal can contain a move for one or more control points; recall the 2-system diagram on the first page. Since we have only one, our cpArray is a singleton.

The open session parameters are a bit of a mystery to me; I'm not clear on how these should properly be used. Please consider this code as exploratory and not canonical until I figure this out.

Opening communications with NTCP and the control plugin

        // Try and open a connection to the server
if(beSecure == true) {
System.out.println("Opening connection to NTCP server at " +
serverURL + " in secure mode");
} else {
System.out.println("Opening connection to NTCP server at " +
serverURL + " in non-secure mode");
}

try {
Server = NtcpHelper.activateNtcpServer(serverURL,
instanceName,
beSecure);
} catch (Exception e) {
System.out.println("Unable to open connection to NTCP server at " +
serverURL + ": " + e);

// since this is main... just exit
System.exit(1);
}

// Open session
System.out.println("Connection opened OK, now sending openSession");

try {
NtcpHelper.openSession(Server, ospArray);
}
catch(Exception e) {
System.out.println("Error opening session with server");
System.exit(1);
}

The communications open in two stages:
  1. NtcpHelper.activateNtcpServer() sets up the data structures but doesn't open a connection.
  2. NtcpHelper.openSession() activates the control plugin, and (usually, depending on the plugin) the connection to the backend control system.
From this, a word of caution - activateNtcpServer can return successfully even if you've misconfigured the server-config.wsdd or have other backend problems. Don't relax until openSession is successful.

Proposing the Move

The next step is to propose the move we want. The control plugin will verify that what we've asked for is acceptable, and the policy plugin can also vet the request. Note that we create a random transaction number and ID. For real code, you'd want either static or predictable (e.g. N, N+1, N+2, ...) transaction IDs.

        // Propose the move/trigger
try {
// Create a random transaction number and ID
transNum = new BigInteger(16, ran);
transName = "TR" + ran.nextInt();

System.out.println("Proposing transaction #" + transNum + " id: "
+ transName);
tstate = NtcpHelper.propose(Server,
transName,
transNum,
cpArray,
timeout,
timeout,
memory);
// Check the return value
if(tstate == org.nees.ntcp.ntcpServer.TransactionStateType.accepted)
{
System.out.println("Transaction proposal accepted. Excellent.");
}
else
{
System.out.println("Transaction rejected! Why? Why!?");
System.exit(1);
}
}
catch(Exception e) {
System.out.println("Error proposing move: "+ e);
System.exit(1);
}

After the propose, always check the transaction state. I'm not sure, but I think it's possible to have a rejection with no exception thrown, meaning that communications are OK but the proposal isn't.

Executing the Move

        // Execute the move
System.out.println("Executing move, may take 60-90 seconds");

try {
NtcpHelper.execute(Server, transName);
}catch(Exception e) {
System.out.println("Error during execution, state of ANCO unknown!");
// Shutdown and bail
try {
NtcpHelper.closeSession(Server);
}catch(Exception e2) {}
System.exit(1);
}
In this case, if the execute fails, we attempt to gracefully close the session before exiting. You could do this more cleanly with a try/catch wrapper for the whole class.

After exectute returns, the move may still be in progress. In production code, each move should be followed by polling the state of the transaction. This code shows the basic idea:
	// In a real system, we need to loop until state == terminated
System.out.println("Execute completed; checking status");

// Check the results
try {
trans = NtcpHelper.getTransaction(Server, transName);
}catch(Exception e) {
System.out.println("Exception during getTransaciton = " + e);
System.exit(1);
}

// Need a loop until (terminated OR too-many-retries) here!
if(trans.getState() == TransactionStateType.terminated) {
System.out.println("Move complete; transaction terminated");
} else {
System.out.println("Move incomplete - transaction not terminated!!");
}

// Just for fun, query the control point
System.out.println("Querying the control point");
Following the execute, getTransaction will include the values of all of the control points. If more time has elapsed, you'd need to call getControlPoint again to get updated data. This should trigger a corresponding request to the control system, which will report the actual table position, and return it to us.
In this case, its redundant since we're getting the value right after the move; it's included here for completeness.
        try {  
System.out.println("transName: " + transName);
cpq = NtcpHelper.getControlPoint(Server, "ANCO");
} catch(Exception e) {
System.out.println("Error querying the control point: " + e);

// Shutdown and exit
try {
System.out.println("Shutting down session and exiting");
NtcpHelper.closeSession(Server);
}catch(Exception e2) {}
System.exit(1);
}
The following code, contributed by Xin Feng, prints out the values of the two controlPoint variables.
	//print out control point
System.out.println("Control point name: " + cpq.getControlPointName());
ControlPointGeomParameterType param1 = cpq.getControlPointType(0);
ControlPointGeomParameterType param2 = cpq.getControlPointType(1);

System.out.println("Control point param1: " + param1.getValue());
System.out.println("Control point param2: " + param2.getValue());

Notes when using the DummyPlugin

If you are running this code with the dummy plugin, the getControlPoint call will fail, as that plugin does not recognize the name of the control point. To make it work, change
cpName = "ANCO" to
cpName = "dummy"
There is also a known bug in the dummy plugin that it will never mark a transaction as completed. So the nclient code output will look something like this:
     [java] Opening connection to NTCP server at http://localhost:8090/ogsa/services/nees/ntcp/ in non-secure mode
[java] Connection opened OK, now sending openSession
[java] - Logging is working
[java] - Date: Wed Mar 24 17:06:33 CST 2004
[java] - Version: Apache-XML-Security-J 1.0.4
[java] Proposing transaction #2404 id: TR461700679
[java] Transaction proposal accepted. Excellent.
[java] Executing move, may take 60-90 seconds
[java] Execute completed; checking status
[java] Move incomplete - transaction not terminated!!
[java] Querying the control point
[java] Closing NTCP session
[java] Done.

Cleanup and Shutdown

        // Shut down the ntcp connection
System.out.println("Closing NTCP session");
try
{
NtcpHelper.closeSession(Server);
}
catch(Exception e)
{
System.out.println("Error closing the session = " + e);
System.exit(1);
}

System.out.println("Done.");

return;
}
}

Not much to say here.

Discussion and Comments

At this point you've now seen a bare-bones NTCP client, and know how to run it. (ant run, in $HOME/code/nclient). Next, we can look at changing control plugins from the dummy to something more useful.

One thing not done in the code is the command line - we hardwire the NTCP servername, control point string, etc. All of these are good candidates for command-line arguments.

Next page is on writing your own NTCP control plugin.