Creating externally accessible Tasks
One of the main benefits of JICOS is that arbitrary code can be run. By using RMI, a class written by the developer can be imported into the JICOS system, and distributed across many machines. One of the main drawbacks is that this can only be done in Java. By adding some additional methods, the Task will become accessible by an external client that is not Java. This section describes those modifications necessary.
In order for a task to be available to an external client, it must implement the XmlConverter interface. This interface defines seven of the required functions (the eighth being a no-argmument constructor).
XmlConverter interface (without comments)
public interface XmlConverter {
public static final int STYLESHEET_Unknown = 0;
public static final int STYLESHEET_Xml = 1;
public static final int STYLESHEET_Html = 2;
public String toXml( String prefix ) throws Exception;
public boolean fromXml( ExternalData externalData ) throws Exception;
public Object createInput( ExternalData externalData ) throws Exception;
public Shared createShared( ExternalData externalData ) throws Exception;
public XmlDocument createResult( Object result ) throws Exception;
public org.w3c.dom.Document getStyleSheet( int styleSheetType );
public String toHtmlString( XmlDocument result, String hostPort );
} |
It would be difficult to be able to determine a priori the arguments necessary for constructing the the Task. As a result, all externally accessible tasks must define a publicly visible, no argument Constructor. The collector, after determining the class, will create an instance of the Task. After removing any top-level element (such as ExternalRequest), the new task is then populated.
ExternalRequestProcessor.createTask()
private Task createTask( ExternalData externalData )
{
String className = externalData.getValue( TASKNAME_ATTRIBUTE );
Class taskClass = Class.forName( className );
Task task = (Task)taskClass.newInstance();
externalData = externalData.removeWrapper();
((XmlConverter)task).fromXml( externalData );
return( task );
}
|
A somewhat subtle problem can show up in this function. If the Task that was created does not implement the XmlConverter interface, this will throw a ClassCastException when trying to invoke the fromXML() function of the new task.
Illustrated above is the creation and population of a Task by data from an external client. The new task instance becomes populated by invoking the fromXML() function.
In the case of Fibonacci, there is only one piece of information necessary from the client: the stopping point. In the case of the traveling salesman, there may be a value representing the distance to and from 100 different cities.
Fibonacci.fromXml()
public boolean fromXml( ExternalData externalData )
{
boolean success = false;
String n = null;
if (null != (n = externalData.getValue( "/Fibonacci/n" )))
{
try
{
setN( Integer.parseInt( n ) );
success = true;
}
catch (NumberFormatException formatException)
{
// ignore
}
}
return (success);
} |
The Input Object is a peice of unchanging data that is propogated to each machine taking part in the computation, and is available in the Task's "Environment". For a task like the traveling salesman problem, this would be the distances between all the cities. The Fibonacci Task just contains a single integer, and doesn't use it.
Fibonacci.createInput()
public Object createInput( ExternalData externalData )
{
return ((Object) null);
} |
The Shared object is also part of the Task's environment, but is allowed to be changed. The changes propogate out to all other hosts. This is useful in "branch and bound" type problems like the Traveling Salesman Problem. If, during the creation of a tour (the "branch"), you have exceeded the current best tour, you can stop searching and move on to the next tour. If you come up with a tour that beats the current best tour, let all the Tasks running on all the machines know about this new tour length (the "bound").
However, in a Task as simple as Fibonacci, there is no need for a Shared Object.
Fibonacci.createShared()
public Shared createShared( ExternalData externalData )
{
return ((Shared) null);
} |
Once JICOS has an answer to the computation it will return it to the client which, in this case, an ExternalRequestProcessor. The answer needs to be converted to XML for transmission to the client. This is done with the createResult() method.
For example, the result of a Fibonacci computation is the nth number of the sequence returned as an Integer. This value is wrapped up in some XML under the element /ExternalResponse/Fibonacci/FofN.
Fibonacci.createResult()
public XmlDocument createResult( Object result ) throws Exception
{
String xml = null;
if (null == result)
{
throw new NullPointerException( "Result cannot be null" );
}
else
{
int FofN = ((Integer) result).intValue();
xml
= "<?xml version=\"1.1\" encoding=\"UTF-8\" ?>\r\n"
+ "<ExternalResponse>\r\n"
+ " <Fibonacci>\r\n"
+ " <n xsi:type=\"xsd:Integer\">" + this.n + "</n>\r\n"
+ " <FofN xsi:type=\"xsd:Integer\">" + FofN + "</FofN>\r\n"
+ " </Fibonacci>\r\n"
+ "</ExternalResponse>\r\n"
;
}
return (new XmlDocument( xml ));
} |
A variety of SAX and DOM exceptions are possible in the creation of the XmlDocument. Instead of exposing them all, they are hidden in the all-encompasing Exception in order to hide implementation details.
Since the Fibonacci task does not provide an HTML → XML translation stylesheet, it performs the conversion manually. The function toHtmlString() requires two values: the XML result, and the machine and port on which the CollectorHttp is currently running.
Fibonacci.toHtmlString()
public String toHtmlString( XmlDocument xmlResult, String hostPort )
{
String strFib = null;
// Fix the hostname/port.
if (null == hostPort)
{
hostPort = "localhost";
}
// Get the result if it exists.
if (null != xmlResult)
{
strFib = xmlResult.getValue( "/ExternalResponse/Fibonacci/FofN" );
if ("".equals( strFib ))
{
strFib = null;
}
}
int number = this.n;
if (0 >= number)
{
number = DEFAULT_Number;
}
String html
= "<HTML>\r\n"
+ "<HEAD>\r\n"
+ " <TITLE>Fibonacci Solver</TITLE>\r\n"
+ "</HEAD>\r\n"
+ "<BODY>\r\n"
+ CollectorHttp.jicosHtmlHeader()
+ "<CENTER><H2>Fibonacci Solver</H2></CENTER>\r\n"
+ "\r\n"
;
// If there is an answer, format it.
if( null != strFib )
{
html = html
+ "<!-- Answer from previous request. -->\r\n"
+ "<!>\r\n"
+ "<FONT COLOR=green>Previous Result:</FONT><BR>\r\n"
+ "<BLOCKQUOTE>\r\n"
+ "<TABLE>\r\n"
+ " <TR>\r\n"
+ " <TD VALIGN=\"center\">Fibonacci( <B>" + this.n
+ "</B> ) = </TD>\r\n"
+ " <TD VALIGN=\"center\"><FONT COLOR=blue SIZE=\"+2\">"
+ strFib + "</FONT></TD>\r\n"
+ " </TR>\r\n"
+ "</TABLE>\r\n"
+ "</BLOCKQUOTE>\r\n"
+ "<BR>\r\n"
;
}
// A request for a Fibonacci number.
html = html
+ "\r\n"
+ "<!-- Ask for new number (start form). -->\r\n"
+ "<!>\r\n"
+ "<BR><BR>\r\n"
+ "<FORM ACTION=\"http://" + hostPort + ExternalRequest.TOP_LEVEL
+ "\" METHOD=\"post\">\r\n"
+ "<INPUT TYPE=\"hidden\"\r\n"
+ " NAME=\"" + ExternalRequestProcessor.TASKNAME_ATTRIBUTE
+ "\"\r\n"
+ " VALUE=\"" + this.getClass().getName() + "\"></INPUT>\r\n"
+ "<CENTER>\r\n"
+ "<TABLE>\r\n"
+ " <TR>\r\n"
+ " <TD BGCOLOR=\"black\">\r\n"
+ " <TABLE BORDER=0 CELLPADDING=\"3px\" BGCOLOR=\"#EEEEEE\">\r\n"
+ " <TR>\r\n"
+ " <TD WIDTH=\"100\" ALIGN=\"right\"><FONT COLOR=\"blue\" "
+ "SIZE=\"+1\">Number:</FONT></TD>\r\n"
+ " <TD><INPUT TYPE=\"text\" SIZE=\"5\" VALUE=\"" + number
+ "\"\r\n"
+ " NAME=\"" + ExternalRequest.TOP_LEVEL
+ "/Fibonacci/n\"></INPUT></TD>\r\n"
+ " <TD WIDTH=\"100\" ALIGN=\"left\"><INPUT TYPE=\"submit\">"
+ "</INPUT></TD>\r\n"
+ " </TR>\r\n"
+ " </TABLE>\r\n"
+ " </TD>\r\n"
+ " </TR>\r\n"
+ "</TABLE>\r\n"
+ "</CENTER>\r\n"
+ "<BR><BR>\r\n"
+ "<!>\r\n"
+ CollectorHttp.createResponseSelect( null )
+ "<!>\r\n"
+ "</FORM>\r\n"
+ "<!>\r\n"
+ "<!-- End form. -->\r\n"
+ CollectorHttp.jicosHtmlFooter()
+ "</BODY>\r\n"
+ "</HTML>\r\n"
;
return (html);
} |
One feature of the CollectorHttp is that it will accept "GET" messages as well as "POST" messages. When the collector receives a GET for a Task, it will construct and populate the Task, then pass a null result to the method.
fibonacci.html
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 TRANSITIONAL//EN">
<HTML>
<HEAD>
<TITLE>Fibonacci Solver</TITLE>
<META HTTP-EQUIV=Refresh CONTENT="0; URL=http://localhost:8181/ExternalReques¶
t/jicos.examples.external.fibonacci.Fibonacci">
</HEAD>
<BODY>
<STYLE TYPE="text/css">
A {
font-family: monospace;
text-decoration: none;
}
</STYLE>
<CENTER>
<H2>Fibonacci Solver</H2>
<P>If this page doesn't automatically refresh, please go to<BR>
<BR>
<A HREF="http://localhost:8181/ExternalRequest/jicos.examples.external.fibonacc¶
i.Fibonacci">
http://localhost:8181/ExternalRequest/jicos.examples.external.fibonacci.Fibonac¶
ci</A><BR>
<BR>
or wherever the CollectorHttp is running.</P>
</CENTER>
</BODY>
</HTML>
|
The ¶ represents a line continuation.
If you wish to look at the complete Fibonacci.java, or the complete TSP (traveling salesman problem), please do so.
|