[MOBY-guts] biomoby commit

Andreas Groscurth groscurt at dev.open-bio.org
Wed Nov 26 08:53:43 UTC 2008


groscurt
Wed Nov 26 03:53:43 EST 2008
Update of /home/repository/moby/moby-live/Java/src/main/org/biomoby/client
In directory dev.open-bio.org:/tmp/cvs-serv17488/src/main/org/biomoby/client

Modified Files:
	MobyRequest.java CentralImpl.java BaseClient.java 
Log Message:
changes done enabling the authentication for services
moby-live/Java/src/main/org/biomoby/client MobyRequest.java,1.40,1.41 CentralImpl.java,1.57,1.58 BaseClient.java,1.12,1.13
===================================================================
RCS file: /home/repository/moby/moby-live/Java/src/main/org/biomoby/client/MobyRequest.java,v
retrieving revision 1.40
retrieving revision 1.41
diff -u -r1.40 -r1.41
--- /home/repository/moby/moby-live/Java/src/main/org/biomoby/client/MobyRequest.java	2008/11/14 20:32:02	1.40
+++ /home/repository/moby/moby-live/Java/src/main/org/biomoby/client/MobyRequest.java	2008/11/26 08:53:43	1.41
@@ -1,1019 +1,1028 @@
-package org.biomoby.client;
-
-import java.io.*;
-import java.util.*;
-
-import javax.xml.namespace.QName;
-import javax.xml.parsers.DocumentBuilder;
-import javax.xml.parsers.DocumentBuilderFactory;
-import javax.xml.parsers.ParserConfigurationException;
-import javax.xml.rpc.Service;
-import javax.xml.transform.TransformerException;
-
-import org.apache.axis.client.Call;
-import org.apache.axis.message.MessageElement;
-import org.apache.xml.utils.PrefixResolver;
-import org.apache.xml.utils.PrefixResolverDefault;
-import org.apache.xpath.XPath;
-import org.apache.xpath.XPathContext;
-import org.apache.xpath.objects.XNodeSet;
-import org.apache.xpath.objects.XObject;
-
-import org.biomoby.shared.*;
-import org.biomoby.shared.data.*;
-import org.biomoby.shared.parser.MobyTags;  // defined the Moby XML element names
-import org.biomoby.w3c.addressing.EndpointReference;
-import org.omg.lsae.notifications.AnalysisEvent;
-
-import org.w3c.dom.*;
-
-/**
- * This class handles the WSDL transaction to request a response
- * from a remote SOAP Web service that handles the 
- * <a href="http://www.biomoby.org">MOBY</a> format.  It depends on 
- * having already retrieved the definition of the Web service via 
- * the MOBY central registry using the 
- * <a href="http://www.biomoby.org/moby-live/Java/docs/index.html">jMOBY</a> API,
- * and for now it uses the 
- * <a href="http://ws.apache.org/axis/index.html">Apache
- * Axis</a> Web services framework, as well as Apache Xalan.  There are code comments for the
- * few lines that rely on Axis classes rather than the JAX-RPC interfaces.
- *
- * @author Paul Gordon gordonp at ucalgary.ca
- */
-public class MobyRequest{
-
-    protected MobyService mobyService = null;
-    protected MobyContentInstance inputData = null;
-    protected MobyContentInstance outputData = null;
-    protected Central mobyCentral = null;
-    protected PrefixResolver mobyPrefixResolver = null;
-    
-    protected Hashtable wsdlCache = null;
-    protected String lastWsdlCacheKey = null;
-    protected DocumentBuilder docBuilder = null;
-    protected Service service = null;
-    
-    protected Class stringType;
-    protected static boolean debug = false;
-    protected PrintStream debugPS = System.err;
-    protected XPathContext xpath_context;
-    protected String responseString = null;
-
-    private XPath stringEncodedXPath;
-    private XPath base64EncodedXPath;
-    private XPath simpleChildXPath;
-    private XPath collectionChildXPath;
-
-    private int autoID = 0;
-
-    // Used as invocation callback if MobyRequest is acting as a server,
-    // or is executing services as a client asynchronously
-    private Vector<MobyRequestEventHandler> eventHandlers; 
-
-    /**
-     * Default constructor.  You should have a Central instance around since you're going to 
-     * be retrieving MobyServices to pass into here. Lets reuse it.
-     *
-     * @param central An instance of a Moby central object so we can make requests about object types, etc.
-     * @throws ParserConfigurationException if JAXP doesn't have any valid DOM-building XML parsers set up for use
-     */
-    public MobyRequest(Central central) throws ParserConfigurationException{
-	mobyCentral = central;
-	wsdlCache = new Hashtable();
-
-	eventHandlers = new Vector<MobyRequestEventHandler>();
-
-	DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
-	dbf.setNamespaceAware(true);	
-	docBuilder = dbf.newDocumentBuilder();
-
-	try{
-	    stringType = Class.forName("java.lang.String");
-	}catch(ClassNotFoundException classe){
-	    debugPS.println("WARNING: Something is very wrong, could not find Class definition of String: " + classe);
-	}
-
-	xpath_context = new XPathContext();
-	mobyPrefixResolver = new MobyPrefixResolver();
-
-	// Now compile the XPath statements that will be used fetch data from the server response
-	try{
-	    base64EncodedXPath = new XPath("//*[starts-with(substring-after(@"+
-                                           MobyPrefixResolver.XSI1999_PREFIX+
-                                           ":type, ':'), \"base64\") or starts-with(substring-after(@"+
-                                           MobyPrefixResolver.XSI2001_PREFIX+
-                                           ":type, ':'), \"base64\")]", null, 
-					   mobyPrefixResolver, XPath.SELECT);
-	    stringEncodedXPath = new XPath("//*[substring-after(@"+
-                                           MobyPrefixResolver.XSI1999_PREFIX+
-                                           ":type, ':')=\"string\" or substring-after(@"+
-                                           MobyPrefixResolver.XSI2001_PREFIX+
-                                           ":type, ':')=\"string\"] | //"+
-					   MobyPrefixResolver.SOAP_ENC_PREFIX+":string", null, 
-					   mobyPrefixResolver, XPath.SELECT);
-	    simpleChildXPath = new XPath("moby:Simple | Simple", null, 
-					 mobyPrefixResolver, XPath.SELECT);
-	    collectionChildXPath = new XPath("moby:Collection | Collection", null, 
-					     mobyPrefixResolver, XPath.SELECT);
-	}
-	catch(TransformerException te){
-	    debugPS.println("Syntax error encountered while compiling XPath " +
-			    "statements for internal use (code bug?): " + te);
-	}
-	setDebugMode(System.getProperty("moby.debug") != null);
-    }
-
-    /**
-     * @param mode if true, debugging information is printed to the stream returned by getDebugOutputStream
-     */
-    public void setDebugMode(boolean mode){
-	debug = mode;
-    }
-
-    /**
-     * Standard error is used unless this method is called.
-     *
-     * @param ps the OutputStream to which debugging information is sent.
-     * @throws IllegalArgumentException if the stream is null
-     */
-    public void setDebugPrintStream(PrintStream ps) throws IllegalArgumentException{
-	if(ps == null){
-	    throw new IllegalArgumentException("The OutputStream specified to MobyRequest was null");
-	}
-	debugPS = ps;
-    }
-
-    /**
-     * @return the instance of the class implementing Central that we are using
-     */
-    public Central getCentralImpl(){
-	return mobyCentral;
-    }
-
-    /**
-     * @param mobyservice the MobyService that should be executed when invokeService is called
-     */
-    public void setService(MobyService mobyservice){
-	if(mobyservice == null){
-	    mobyService = null;
-	}
-	else if(mobyService == null || !mobyservice.equals(mobyService)){
-	    mobyService = mobyservice;
-	}
-    }
-
-    /**
-     * @return the MobyService that will be executed when invokeService is called
-     */
-    public MobyService getService(){
-	return mobyService;
-    }
-
-    /**
-     * @return the Raw MOBY XML response as a string
-     */
-    public String getResponseXML(){
-	return responseString;
-    }
-
-    /**
-     * Sets the input data for the MOBY service request. The length of the input array, and the
-     * number of input parameters required by the service must be equal when invokeService() is called.
-     * This method strictly enforces that the input be of the appropriate type for the service.
-     *
-     * Note that there is no requirement to use MobyDataInstance.setXmlMode() before passing in
-     * data, this class will temporarily set the XML mode of the data when it is required.
-     *
-     * @throws IllegalArgumentException if the input does not fit the criteria of the service (e.g. wrong data type)
-     */
-    public void setInput(MobyContentInstance data) throws MobyException{
-	inputData = data;
-    }
-
-    /**
-     * Takes the data in the array, with their current articleNames, as input for the service 
-     */
-    public void setInput(MobyDataInstance[] data) throws MobyException{
-	MobyDataJob job = new MobyDataJob();
-	for(MobyDataInstance param: data){
-	    job.put(param.getName(), param);
-	}
-	inputData = new MobyContentInstance();
-	inputData.put(job);
-    }
-
-    /**
-     * Convenience method to run services that take one argument.  If the service
-     * requires the input to have a name, it will be automatically assigned.
-     */
-    public void setInput(MobyDataInstance datum) throws MobyException{
-	setInput(datum, "");
-    }
-
-    /**
-     * Convenience method to run services that take one named argument.
-     */
-    public void setInput(MobyDataInstance datum, String paramName) throws MobyException{
-        inputData = new MobyContentInstance(datum, paramName);
-    }
-
-    /**
-     * @return the MobyService that will be executed when invokeService is called
-     */
-    public MobyContentInstance getInput(){
-	return inputData;
-    }
-    
-    /**
-     *  Same functionality as setSecondaryInput(MobyDataSecondaryInstance[])
-     */
-    public void setSecondaryInput(Collection<MobyDataSecondaryInstance> secondaryData) throws MobyException{
-	setSecondaryInput(secondaryData.toArray(new MobyDataSecondaryInstance[secondaryData.size()]));
-    }
-
-    /**
-     * This method will assign the provided secondary parameters to all primary input data currently
-     * in this object.  This is covenient if you are running 100 seqs through BLAST and only want to set
-     * the parameters once.  If you instead want to set secondary input differently for all primary inputs, you'll
-     * need to create a custom MobyContentInstance as input to setInput().
-     *
-     * @throws MobyException if a parameter name is blank, or overrides a primary parameter
-     */
-    public void setSecondaryInput(MobyDataSecondaryInstance[] secondaryData) throws MobyException{
-
-	Iterator queryNames = inputData.keySet().iterator();
-	// For each query
-	while(queryNames.hasNext()){
-	    MobyDataJob queryParams = inputData.get(queryNames.next());
-	    // Set all the secondary params (overwrites any old ones)
-	    for(int i = 0; i < secondaryData.length; i++){
-		String secName = secondaryData[i].getName();
-		if(secName == null || secName.length() == 0){
-		    throw new MobyException("A secondary parameter cannot have a blank name (array index " + i + ")");
-		}
-		if(queryParams.containsKey(secName) && queryParams.get(secName) instanceof MobyPrimaryData){
-		    throw new MobyException("A secondary parameter cannot override an existing primary parameter " +
-					    "with the same name (" + secName + ")");
-		}
-		queryParams.put(secName, secondaryData[i]);
-	    }
-	}
-    }
-
-    /**
-     * @return a vector of MobyDataInstance[], each element of the vector is the collection of response objects for the correspondingly indexed input request.
-     *
-     * @throws MobyException if you try to get the results before calling InvokeService
-     */
-    public MobyContentInstance getOutput() throws MobyException{
-	if(outputData == null){
-	    throw new MobyException("Trying to access MOBY service results " +
-				    "before the service is invoked");
-	}
-	else{
-	    return outputData;
-	}
-    }
-
-    /**
-     * The main method of the class.  If all of the MOBY input objects 
-     * are properly defined according to the Web service definition,
-     * a SOAP request will be sent to the remote server, and the method 
-     * will return one or more MOBY objects (synchronous).  
-     * Call this method after calling setService, and setInput.  If you do not call
-     * setSecondaryInput, the default secondary parameter values will be used.
-     *
-     * @return the results of the remote Web service in response to the give input
-     *
-     * @throws MobyException i.e. there was something wrong with the input, output or remote service's logic
-     * @throws SOAPException i.e. there was a problem with the underlying transaction/transport layer
-     */
-    public MobyContentInstance invokeService() throws Exception, MobyException, SOAPException, NoSuccessException{
-	return mobyService.isAsynchronous() ? invokeService(inputData, new StringBuffer()) : invokeService(inputData, (StringBuffer) null);
-    }
-
-    // Used internally for asynchronous thread calls that all need the XML data
-    // and can't rely on the answer from thread-insensitive getResponseXML()
-    private MobyContentInstance invokeService(MobyContentInstance inData, StringBuffer contentsXML) 
-	throws Exception, MobyException, SOAPException, NoSuccessException{
-	return invokeService(inData, contentsXML, null, 0);
-    }
-
-    private MobyContentInstance invokeService(MobyContentInstance inData, StringBuffer contentsXML, MobyRequestEventHandler handler, int requestId) 
-	throws Exception, MobyException, SOAPException, NoSuccessException{
-
-	if(mobyService == null){
-	    throw new MobyException("Tried to invoke null service from MobyRequest (call setService first)");
-	}
-
-	Element mobyDOM = null;
-	if(mobyService.isAsynchronous()){
-	    // Async is "simpler", because it had to merge DOMs together into a single MobyContentInstance anyway
-	    MobyContentInstance mci = performAsyncSOAPRequest(mobyService, inData, handler, requestId);
-	    StringWriter writer = new StringWriter();
-	    MobyDataUtils.toXMLDocument(writer, mci);
-	    contentsXML.append(writer.toString());
-	    return mci;
-	}
-	else{
-	    String mobyXML = convertMOBYDataToMOBYRequest(inData);
-	    Call call = getServiceFromWSDL();
-	    mobyDOM = performSOAPRequest(call, mobyXML, contentsXML);
-	    // The following parses the DOM and extracts all the appropriate jMOBY objects to represent the XML in Java
-	    return MobyDataUtils.fromXMLDocument(mobyDOM, mobyService.getServiceType().getRegistry());  
-	}
-    }
-
-    protected MobyContentInstance performAsyncSOAPRequest(MobyService mservice, MobyContentInstance inData, 
-							  MobyRequestEventHandler handler, int requestId) 
-	throws Exception{
-	String mobyXML = convertMOBYDataToMOBYRequest(inData);  
-	EndpointReference epr = AsyncClient.sendRequest(mservice, mobyXML);
-
-	// Essentially cloning, so removing ids doesn't change the 
-	// MobyContentInstance "data" (which we will use again later on)
-	MobyContentInstance finalContents = new MobyContentInstance();
-	Set<String> queryIDs = new HashSet<String>(inData.keySet());
-	try {
-	    // Should add some timeout here...
-	    while(!queryIDs.isEmpty()){
-		// todo: make this setable
-		Thread.sleep(5000);
-
-		AnalysisEvent[] events = 
-		    AsyncClient.poll(epr, queryIDs);
-
-		Vector<String> newDataAvailable = new Vector<String>();
-		for(AnalysisEvent event: events){
-		    if(event != null && event.isCompleted()){
-			queryIDs.remove(event.getQueryId());
-			newDataAvailable.add(event.getQueryId());
-		    }
-		}
-
-		if(newDataAvailable.size() > 0){
-		    // Parse and merge the new data into the existing contents
-		    InputStream resultStream = AsyncClient.getResultStream(epr, newDataAvailable);
-		    Element mobyDOM = asyncSoapTextToMobyDOM(resultStream);
-		    MobyContentInstance newResults = MobyDataUtils.fromXMLDocument(mobyDOM, mservice.getServiceType().getRegistry());
-		    // The merge
-		    for(String jobid: newResults.keySet()){
-			finalContents.put(jobid, newResults.get(jobid));
-		    }
-		    
-		    // Inform the handler that some data has been added to the response (for incremental display?)
-		    if(handler != null){
-			MobyRequestEvent mre = new MobyRequestEvent(finalContents, this, mservice, null, requestId);
-			StringWriter xmlWriter = new StringWriter();
-			MobyDataUtils.toXMLDocument(xmlWriter, finalContents);
-			
-			mre.setContentsXML(xmlWriter.toString());
-			if(!queryIDs.isEmpty()){
-			    // Send an update event only if we aren't finished yet.
-			    // If we are finished, the client is going to get this event as the 
-			    // invocation thread finishes up (no need to double up).
-			    handler.processEvent(mre);
-			}
-		    }
-		}
-	    }
-	} catch (Exception e) {
-	    e.printStackTrace();
-	    AsyncClient.destroy(epr);
-	    throw new Exception("Exception occured while polling the service invocation: " + e);
-	}
-
-	return finalContents;
-    }
-
-    private Element asyncSoapTextToMobyDOM(InputStream inStream) throws Exception{
-	Element soapDOM = null;
-	synchronized(docBuilder){
-	    soapDOM = docBuilder.parse(inStream).getDocumentElement();
-	}
-	final boolean IS_ASYNC_SERVICE_CALL = true;
-	return decodeSOAPMessage(soapDOM,  null,  null, IS_ASYNC_SERVICE_CALL);
-    }
-
-    /**
-     * Asynchronous call to invokeService.  A callback to the passed-in handler will be made when 
-     * the response is ready, or there is an exception.
-     *
-     * @return the id that the callback event will return from getID(), allowing a client to distinguish between multiple concurrent invocation callbacks
-     */
-    public synchronized int invokeService(MobyRequestEventHandler handler){
-	int id = autoID++;
-
-	Thread t = new InvocationThread(this, inputData, handler, id);  // see internal class definition below
-	t.start();
-
-	return id;
-    }
-
-    // This is the class that asynchronously calls the service and does a callback to 
-    // the handler specified in the invocation.
-    class InvocationThread extends Thread {
-	MobyContentInstance data;
-	MobyService mservice;
-	MobyRequest mobyRequest; 
-	MobyRequestEventHandler handler;
-	int requestId;
-
-	InvocationThread(MobyRequest mr, MobyContentInstance inData, MobyRequestEventHandler h, int id){
-	    data = inData;
-	    mobyRequest = mr;
-	    mservice = mobyRequest.getService();
-	    handler = h;
-	    requestId = id;
-
-	    // Name the thread after the service being run, mostly for ease of debugging
-	    setName(mservice.getName()+requestId);
-	}
-
-	public void run() {
-	    MobyRequestEvent requestEvent = new MobyRequestEvent(data, mobyRequest, mservice, null, requestId);
-	    // Tell the handler we're starting the request, with the given data
-	    handler.start(requestEvent);
-
-	    MobyRequestEvent responseEvent = null;
-	    MobyContentInstance content = null;
-	    StringBuffer contentsXML = new StringBuffer();  //to be filled in by the RPC call below
-	    try{
-		content = mobyRequest.invokeService(data, contentsXML, handler, requestId); //RPC call...
-	    }
-	    catch(Exception e){
-		responseEvent = new MobyRequestEvent(content, mobyRequest, mservice, e, requestId);
-	    }
-	    catch(Error err){
-		responseEvent = new MobyRequestEvent(content, mobyRequest, mservice, err, requestId);
-	    }
-	    if(responseEvent == null){
-		responseEvent = new MobyRequestEvent(content, mobyRequest, mservice, null, requestId);
-	    }
-	    // We've got the raw XML laying around, so why not provide it unmolested to the callback?
-	    responseEvent.setContentsXML(contentsXML.toString());
-	    handler.processEvent(responseEvent);
-	    handler.stop(mobyRequest, requestId);
-	}
-    }
-
-    public void addEventHandler(MobyRequestEventHandler h){
-	eventHandlers.add(h);
-    }
-
-    public void removeEventHandler(MobyRequestEventHandler h){
-	eventHandlers.remove(h);
-    }
-
-    public void sendResponse(MobyRequestEvent mre){
-	// Not yet implemented, need to conform to some web.xml specification here...
-    }
-
-    /**
-     * This method retrieves from Moby Central a copy of the WSDL document for the service
-     * (or uses an internally cached copy from a previous invocation), and sets the variables for the 
-     * SOAP call appropriately so you can consequently call performSOAPRequest.
-     */
-    protected Call getServiceFromWSDL() throws MobyException, NoSuccessException{
-// 	String wsdl = null;
-
-// 	// Since this is how we retrieve a service from Central, use the same values as the key to the hash
-// 	String wsdlCacheKey = mobyService.getName() + "@" + mobyService.getAuthority();
-
-// 	// This is the same call as last time, so we don't need to change the setup
-// 	if(wsdlCacheKey.equals(lastWsdlCacheKey)){
-// 	    return setCallFromWSDL((String) wsdlCache.get(wsdlCacheKey));
-// 	}
-// 	// We haven't encountered this service yet
-// 	else if(!wsdlCache.containsKey(wsdlCacheKey)){
-// 	    wsdl = mobyCentral.getServiceWSDL(mobyService.getName(), mobyService.getAuthority());
-// 	    wsdlCache.put(wsdlCacheKey, wsdl);
-// 	}
-//         // We've dealt with this one before
-// 	else{
-// 	    wsdl = (String) wsdlCache.get(wsdlCacheKey);
-// 	}
-
-// 	lastWsdlCacheKey = wsdlCacheKey;  // Keep track of the last invocation
-
-	// Get ready to do SOAP
-	return setCallFromWSDL(null);
-    }
-
-    /**
-     * Creates the SOAP Call that will be invoked later.  This should be based on the WSDL document
-     * and parameter information from the MobyService, but these are currently not up to snuff.
-     */
-    protected Call setCallFromWSDL(String wsdl) throws MobyException, SOAPException{
-	if(service == null){
-	    service = new org.apache.axis.client.Service();  // AXIS SPECIFIC This acts as a factory for Calls
-	}
-
-	Call soapCall;
-	try{
-	    soapCall = (Call) service.createCall();//create a fresh Call each time
-	}catch(javax.xml.rpc.ServiceException se){
-	    throw new SOAPException("Could not instatiate call to SOAP Service: " + se);
-	}
-
-	// Should initialize endpoint, etc. This call is AXIS SPECIFIC, otherwise you'll 
-	// have to do the call's info setting manually.
-	//((org.apache.axis.client.Call) soapCall).setSOAPService(soapService); 
-	soapCall.removeAllParameters();
-	soapCall.setTargetEndpointAddress(mobyService.getURL());
-	soapCall.setPortName(new QName("http://biomoby.org/", 
-					   mobyService.getName() + "PortType"));
-	//soapCall.setOperationName(new QName("http://biomoby.org/", 
-	//				    mobyService.getName()));
-	soapCall.setSOAPActionURI("http://biomoby.org/#" + mobyService.getName());
-	return soapCall;
-    }
-
-    /**
-     *  Calls the invoke() method of the JAX-RPC Call interface.
-     */
-    protected Element performSOAPRequest(Call soapCall, String mobyInputXML, StringBuffer contentsXMLOutput) throws SOAPException{
-	// First, turn the input objects into a MOBY XML request
-	String[] mobyXMLInputData = new String[1];
-
-	//Setup
-        mobyXMLInputData[0] = mobyInputXML;
-
-	if(debug)
-	    debugPS.println("returnType just before invoke call is " + soapCall.getReturnType());
-	Object returnedObject = null;
-	try{
-	    returnedObject = soapCall.invoke(new QName("http://biomoby.org/", 
-					    mobyService.getName()), mobyXMLInputData);
-	}
-	catch(Exception e){
-	    e.printStackTrace();
-	    //System.err.println("Input: "+mobyInputXML);
-	    throw new SOAPException("While invoking SOAP Call: " + e);
-	}
-
-	try{
-	    if(debug){
-		debugPS.println("SOAP Response was:\n");
-		debugPS.println(soapCall.getResponseMessage().getSOAPPart().getEnvelope());
-	    }
-	    Element resultDom = ((MessageElement) soapCall.getResponseMessage().getSOAPPart().getEnvelope()).getAsDOM();
-	    return decodeSOAPMessage(resultDom, contentsXMLOutput, mobyInputXML);
-	} catch(Exception e){
-	    e.printStackTrace();
-	    throw new SOAPException("Could not get SOAP response as DOM Element: "+ e);
-	}
-
-    }
-
-    public Element decodeSOAPMessage(Element n, StringBuffer contentsXMLOutput, String inputXML)
-	throws SOAPException, MobyException{
-	return decodeSOAPMessage(n, contentsXMLOutput, inputXML, false);
-    }
-
-    /**
-     * Isolates the MOBY Data from the SOAP message returned by the remote service host.
-     *
-     * @throws SOAPException if the MOBY payload cannot be found in the SOAP message
-     * @throws MobyException if the MOBY message is not well-formed XML
-     *
-     * @return The root element of the MOBY response DOM
-     */
-    public Element decodeSOAPMessage(Element n, StringBuffer contentsXMLOutput, String inputXML, boolean async) 
-	throws SOAPException, MobyException{
-	if(n == null){
-	    throw new SOAPException("SOAP Message given to decode is null");
-	}
-
-	NodeList node_list = null;
-	XPath responseElementXPath = null;
-	try{
-	    if(async){
-		responseElementXPath = new XPath("//"+MobyPrefixResolver.WSRP_PREFIX +
-						 ":"+AsyncClient.WSRP_MULTI_PROPERTY_TAG_NAME+"Response",
-						 null, mobyPrefixResolver, XPath.SELECT);
-	    }
-	    else{
-		responseElementXPath = new XPath("//"+ MobyPrefixResolver.MOBY_TRANSPORT_PREFIX+
-						 ":"+mobyService.getName()+"Response | //" +
-						 mobyService.getName()+"Response | " +
-						 "//"+ MobyPrefixResolver.MOBY_TRANSPORT_PREFIX+
-						 ":"+mobyService.getName() + " | //" +
-						 mobyService.getName(), 
-						 null, mobyPrefixResolver, XPath.SELECT);
-	    }
-     	}catch(TransformerException te){
-            throw new SOAPException("Cannot select SOAP nodes due to exception "+
-				    "while compiling XPath statement (code bug?):" +te);
-	}
-	try{
-	    node_list = runXPath(responseElementXPath, n);
-	}catch(TransformerException te){	  
-            throw new SOAPException("Cannot select SOAP nodes due to exception "+
-				    "while executing XPath statement:" +te);
-	}
-
-	if(node_list == null || node_list.getLength() == 0){
-	    throw new SOAPException("Could not find a response element in SOAP payload (service " +
-				    mobyService.getName() + ")");
-	}
-
-	if(node_list.getLength() > 1){
-	    throw new SOAPException("Found more than one response element in SOAP payload, " +
-				    "unable to resolve ambiguity of the payload (service provider error?)");
-	}
-
-	Node[] responseNodes = null;
-	if(async){
-	    Vector<Node> nodes = new Vector<Node>();
-	    NodeList resultNodeList = node_list.item(0).getChildNodes();
-	    for(int i = 0; resultNodeList != null && i < resultNodeList.getLength(); i++){
-		if(!(resultNodeList.item(i) instanceof Element)){
-		    continue;
-		}
-		Element resultElement = (Element) resultNodeList.item(i);
-		if(resultElement.getLocalName().startsWith(AsyncClient.MOBY_RESULT_PROPERTY_PREFIX)){
-		    nodes.add(resultElement);
-		}
-	    }
-	    responseNodes = nodes.toArray(new Node[nodes.size()]);
-	}
-	else{
-	    responseNodes = new Node[]{node_list.item(0)};
-	}
-
-	Element domRoot = null;  // Where the result will be put
-
-	for(Node responseNode: responseNodes){
-	// Find base64 encoded elements in the SOAP message using XPath and 
-	// replace them with the real decoded contents
-	node_list = null;
-	try{
-            node_list = runXPath(base64EncodedXPath, responseNode);
-        }
-	catch(TransformerException te){
-            throw new SOAPException("Cannot select base64 encoded SOAP nodes due to exception "+
-        			    "while executing XPath statement:" +te);
-	}
-	if(debug && node_list != null){
-	    debugPS.println("There were " + node_list.getLength() + 
-				" base64 encoded elements in the data");
-	}
-
-	// Do decoding for each base64 part found
-	for(int i = 0; node_list != null && i < node_list.getLength(); i++){
-	    org.w3c.dom.Node change = node_list.item(i);
-	    /* Make sure the text data is all put into one contiguous piece for decoding*/
-	    change.normalize(); 
-	    
-	    byte[] decodedBytes = org.apache.axis.encoding.Base64.decode(change.getFirstChild().getNodeValue());
-	    String newText = new String(decodedBytes);
-	    if(debug){
-		debugPS.println("New decoded text is" + newText);
-	    }
-	    
-	    // Swap out this node for the decoded data
-	    change.getParentNode().replaceChild(n.getOwnerDocument().createTextNode(new String(decodedBytes)), 
-						change);
-	}
-
-	// Now see if there are any strings that need decoding
-	node_list = null;
-	try{
-            node_list = runXPath(stringEncodedXPath, responseNode);
-        }
-	catch(TransformerException te){
-            throw new SOAPException("Cannot select string encoded SOAP nodes due to exception "+
-        			    "while executing XPath statement:" +te);
-	}
-
-	// Do concatenation for each plain string part found
-	for(int i = 0; node_list != null && i < node_list.getLength(); i++){
-	    org.w3c.dom.Node change = node_list.item(i);
-	    /* Make sure the text data is all put into one contiguous piece for decoding*/
-	    change.normalize();
-	    String plainString = "";
-	    int j = 0;
-	    for(NodeList children = change.getChildNodes(); 
-		children != null && j < children.getLength();
-		j++){
-		Node child = children.item(j);
-		if(child instanceof CDATASection || child instanceof Text){
-		    plainString += child.getNodeValue();
-		    if(debug){
-			debugPS.println("Plain string is now " + plainString);
-		    }
-		}
-	    }
-
-	    // Swap out this node for the decoded data
-	    change.getParentNode().replaceChild(n.getOwnerDocument().createCDATASection(plainString), change);
-	}
-	if(debug && node_list != null){
-	    debugPS.println("There were " + node_list.getLength() + 
-				" XML Schema string encoded elements in the data");
-	}
-
-	// Parse the MOBY XML document payload
-	responseNode.normalize();
-	NodeList children = responseNode.getChildNodes();
-	if(children == null){
-	    throw new MobyException("The MOBY payload has no contents at all"); 
-	}
-	if(children.getLength() != 1){
-	    if(debug){
-		debugPS.println("Warning: MOBY Payload appears to have more than " +
-			       "just text in it, skipping the non-text sections");
-	    }
-	}
-
-	Element predefinedDOM = null;  // Choice of ripping DOM Element for moby payload out of SOAP DOM
-	String localResponseString = "";  // or storing raw XML strings, to be converted to a DOM later
-	for(int j = 0; j < children.getLength(); j++){
-	    Node child = children.item(j);
-	    if(child instanceof CDATASection || child instanceof Text){
-		// Unescape XML special characters in the string, so we can later on 
-		// parse the payload as regular XML.
-		// Ignore whitespace-only node
-		if(child.getNodeValue().matches("^\\s+$")){
-		    continue;
-		}
-		if(debug){
-		    debugPS.println("Concatenating text in response " + child.getNodeValue()); 
-		}
-		localResponseString += child.getNodeValue();//.replaceAll("&lt;", "<").replaceAll("&gt;", ">").replaceAll("(&amp;|&#x46;)", "&");
-	    }
-	    if(child instanceof Element && child.getLocalName().equals(MobyTags.MOBY)){
-		debugPS.println("Warning: The MOBY contents was found as raw XML inside the SOAP response!\n" +
-				"This is illegal according to the MOBY-API, please inform the service\n " +
-				" provider, as parsing such text may not be supported in the future");
-		localResponseString = null;
-		// Store the moby payload root element's DOM represntation, so we don't
-		// have to serialize it to the localResponseString and then parse it out 
-		// again (that would be wasteful).
-		predefinedDOM = (Element) child;
-		break;
-	    }
-	}
-
-	if(localResponseString != null){
-	    if(localResponseString.length() == 0){
-		throw new MobyException("The MOBY payload has no text contents at all"); 
-	    }
-	    if(Character.isWhitespace(localResponseString.charAt(0))){
-		localResponseString = localResponseString.trim();
-	    }
-	}
-
-	// Check if the payload is an XML document.  If not, try a last ditch effort 
-	// by base64 decoding the contents.  This is technically not allowable in the 
-	// MOBY spec, but we are being lenient.
-	if(localResponseString != null && !localResponseString.startsWith("<?xml")){
-	    // Is the XML declaration missing?
-	    if(localResponseString.startsWith("<moby:MOBY") || localResponseString.startsWith("<MOBY")){
-		localResponseString = "<?xml version=\"1.0\"?>\n"+localResponseString;
-		debugPS.println("Warning: The MOBY contents was missing an XML declaration, but it is " +
-				"required by the MOBY API, and may stop working in the future without it.  Please " +
-				"contact the client's provider to correct this.");
-	    }
-	    else{
-		String oldResponse = localResponseString;
-		localResponseString = new String(org.apache.axis.encoding.Base64.decode(localResponseString));
-		if(!localResponseString.startsWith("<?xml")){
-		    throw new MobyException("The SOAP payload defining the MOBY contents " +
-					    "does not start with the xml processing instruction, and is therefore not " +
-					    "an XML document, as specified in the MOBY API. " +
-					    "Please contact the service provider.  Contents was: " + 
-					    oldResponse);
-		}
-		debugPS.println("Warning: The MOBY contents was needlessly base64 encoded (the SOAP " +
-				"envelope does this for you).  It has been decoded, but this is not " +
-				"part of the MOBY API, and may stop working in the future.  Please " +
-				"contact the service provider to correct this.");
-	    }
-	}
-
-	// A bit of a hack: most MOBY data represented in string form
-	// should have its formatting preserved (e.g. BLAST report), yet
-	// no-one uses the xml:space="preserve" attribute.  This causes problems
-	// later on because once the document is parsed, there is no way to get the 
-	// spaces back!  I set the attribute explicitly at the top level of each data
-	// element to compensate.  Unless of course this was already done.
-	if(localResponseString != null && localResponseString.indexOf("xml:space=\"preserve\"") == -1){
-	    localResponseString = localResponseString.replaceAll("<String", "<String xml:space=\"preserve\"");
-	}
-
-	try{
-	    if(async && domRoot != null){
-		// We will actually be appending several full MOBY messages together, so 
-		// we need to do a DOM-meld at the mobyData tag level.
-		Element newContentsTag = null;
-		if(predefinedDOM != null){  // we have the MOBY element as a DOM Element already from the SOAP DOM
-		    newContentsTag = MobyPrefixResolver.getChildElement(predefinedDOM, MobyTags.MOBYCONTENT);
-		}
-		else{
-		    synchronized(docBuilder){
-			Document newDoc = docBuilder.parse(new ByteArrayInputStream(localResponseString.getBytes()));
-			newContentsTag = MobyPrefixResolver.getChildElement(newDoc.getDocumentElement(), MobyTags.MOBYCONTENT);
-		    }
-		}
-		// Find the mobyContents tag under the root MOBY tag...
-		Element existingContentsTag = MobyPrefixResolver.getChildElement(domRoot, MobyTags.MOBYCONTENT);
-		NodeList newJobTags = newContentsTag.getChildNodes();
-		for(int i = 0; newJobTags != null && i < newJobTags.getLength(); i++){
-		    // Service notes blocks must be merged
-		    if(newJobTags.item(i) instanceof Element &&
-		       newJobTags.item(i).getLocalName().equals(MobyTags.SERVICENOTES)){
-			Element existingServiceNotes = 
-			    MobyPrefixResolver.getChildElement(existingContentsTag, MobyTags.SERVICENOTES);
-			if(existingServiceNotes == null){
-			    existingContentsTag.appendChild(newJobTags.item(i));
-			}
-			else{
-			    NodeList newServiceData = newJobTags.item(i).getChildNodes();
-			    for(int j = 0; newServiceData != null && j < newServiceData.getLength(); j++){
-				existingServiceNotes.appendChild(newServiceData.item(j));
-			    }
-			}
-		    }
-		    else{  //everything else is at the same level (i.e. mobyData blocks)
-			existingContentsTag.appendChild(newJobTags.item(i));
-		    }
-		}
-	    }
-	    else{  //synchronous service call, or first async call (which needs to create a doc anyway)
-		// Synchronized to avoid Xerces exception FWK005: concurrent parsing is disallowed
-		if(predefinedDOM != null){  // we have the MOBY element as a DOM Element already from the SOAP DOM
-		    domRoot = predefinedDOM;
-		}
-		else{
-		    synchronized(docBuilder){
-			domRoot = docBuilder.parse(new ByteArrayInputStream(localResponseString.getBytes())).getDocumentElement();
-		    }
-		}      
-	    }
-	} catch(org.xml.sax.SAXException saxe){
-	    throw new MobyException("The SOAP payload defining the MOBY Result " +
-				    "could not be parsed: " + saxe);
-	} catch(java.io.IOException ioe){
-	    throw new MobyException("The SOAP payload defining the MOBY Result " +
-				    " could not be read (from a String!)" + ioe);
-	}
-
-	// Now, either save the xml we got in the class instance member (for synchronous calls to MobyRequest)
-	// or in the StringBuffer given as a parameter to this function (async calls)
-	if(contentsXMLOutput != null){
-	    contentsXMLOutput.append(localResponseString);
-	}
-	else{
-	    responseString = localResponseString;
-	}
-	} // end for responseNode in responseNodes
-
-	// release resources related to the Xpath execution, since we won't be using this doc anymore
-	releaseXPath(n);
-	    
-	return domRoot;
-    }
-
-    public String convertMOBYDataToMOBYRequest(MobyDataInstance data) throws MobyException{
-        return convertMOBYDataToMOBYRequest(new MobyContentInstance(data, ""));
-    }
-
-    /**
-     * Creates an XML representation of the data, renamed to fit the needs of the service if necessary,
-     * and adding any secondary parameter default values if not already specified in the incoming data.
-     *
-     * @param data the array of input parameters to put in a MOBY XML request
-     *
-     * @return the XML representation of the input data
-     */
-    public String convertMOBYDataToMOBYRequest(MobyContentInstance data) throws MobyException{
-
-	MobyData[] inputs = mobyService.getPrimaryInputs();
-	MobySecondaryData[] secondaries = mobyService.getSecondaryInputs();
-
-	// Make sure the number of input args is correct for each query being submitted
-	for(Map.Entry<String,MobyDataJob> entry: data.entrySet()){
-	    String queryName = entry.getKey();
-	    MobyDataJob query = entry.getValue();
-
-	    // Additionally, we check if they are MobyDataInstances below
-	    Map<String,MobyPrimaryData> primaryParams = new HashMap<String,MobyPrimaryData>();
-	    Map<String,MobySecondaryData> secondaryParams = new HashMap<String,MobySecondaryData>();
-
-	    // To store the primary input parameter name as given by the user, 
-	    // in case we need it later on for parameter renaming...
-	    String primaryParamName = null;  
-
-	    for(Map.Entry<String,MobyDataInstance> subentry: query.entrySet()){
-		String name = subentry.getKey();
-		MobyDataInstance param = subentry.getValue();
-		if(param == null){
-		    throw new MobyException("Query " + queryName + 
-					    " contained a null input parameter (" + name + ")");
-		}
-		else if(param instanceof MobyPrimaryData){
-		    primaryParams.put(name, (MobyPrimaryData) param);
-		    primaryParamName = name;
-		}
-		else if(param instanceof MobySecondaryData){
-		    secondaryParams.put(name, (MobySecondaryData) param);
-		}
-		else{
-		    System.err.println("Input parameter " + name + " (query " + queryName +
-				       ") was not a MobyPrimaryData or MobySecondaryData " +
-				       "as expected, but rather was of class " + param.getClass().getName());
-		}
-	    }
-
-	    if(inputs != null && inputs.length != primaryParams.size()){
-		throw new MobyException("Service " + mobyService.getName() + " was provided " + 
-					primaryParams.size() + 
-					" primary input parameter(s), but takes " + inputs.length + 
-					" (query " + queryName + ")");
-	    }
-	    if(secondaries != null){
-		// If no secondaries provided, fill them in by default
-		if(secondaries.length != 0){
-		    for(MobySecondaryData secondary: secondaries){
-			if(!secondaryParams.containsKey(secondary.getName())){
-			    if(debug){
-				System.err.println("Setting default secondary param value for missing param " + secondary);
-			    }
-			    query.put(secondary.getName(), new MobyDataSecondaryInstance(secondary));
-			}
-		    }
-		}
-		if(secondaries.length != secondaryParams.size()){
-		    throw new MobyException("Service " + mobyService.getName() + " was provided " + 
-					    secondaryParams.size() + 
-					    " secondary input parameter(s), but takes " + secondaries.length +
-					    " (query " + queryName + ").  Extra secondary" +
-					    " parameters must have been specified");
-		}
-	    }
-	    
-	    // If there was one anonymous input, assign the name automatically in
-	    // the case the service requires it to be named.  This is the only
-	    // unambiguous case in which we can do this.
-	    if(inputs.length == 1){
-		String serviceParamName = inputs[0].getName(); // name as req'd by the service
-
-		// name isn't the same as required currently
-		if(serviceParamName != null && serviceParamName.length() > 0 && 
-		   !serviceParamName.equals(primaryParamName)){
-		    // take out the old parameter
-		    MobyPrimaryData theInputToRename = (MobyPrimaryData) query.remove(primaryParamName);
-
-		    // Add in the same parameter, but with the appropriate name
-		    query.put(serviceParamName, (MobyDataInstance) theInputToRename);
-		}
-	    }
-	}
-
-	ByteArrayOutputStream mobyRequest = new ByteArrayOutputStream();
-	try{
-	    MobyDataUtils.toXMLDocument(mobyRequest, data);
-	}
-	catch(MobyException me){
-	    throw me;
-	}
-	catch(Exception e){
-	    e.printStackTrace();
-	    throw new MobyException("Could not create MOBY payload XML from input data: " +e);
-	}
-
-	if(debug){
-	    debugPS.println("Input to MOBY Service is:");
-	    debugPS.print(mobyRequest.toString());
-	}
-	
-	return mobyRequest.toString();
-    }
-
-    /**
-     * A method that sets up the execution environment for and runs a compiled XPath statement against a DOM node
-     * You should call releaseXPath when you're done with the results
-     * @return the list of Nodes that satisfy the XPath  in this Node's context
-     */
-    protected NodeList runXPath(XPath xpath, Node n) throws TransformerException{
-	NodeList result = null;
-	int dtm_node_handle = xpath_context.getDTMHandleFromNode(n);
-	PrefixResolver node_prefix_resolver = new PrefixResolverDefault(n);
-	XObject xobject = xpath.execute(xpath_context, n, node_prefix_resolver);
-	if(xobject instanceof XNodeSet){
-	    result = ((XNodeSet) xobject).nodelist();
-	}
-	else if(debug && xobject != null){
-	    debugPS.println("Output of XPath was not a XNodeSet as expected, found " + xobject.getClass().getName());
-	    debugPS.flush();
-	}
-	return result;
-    }
-
-    protected void releaseXPath(Node n){
-	xpath_context.release(xpath_context.getDTM(xpath_context.getDTMHandleFromNode(n)), false);
-    }
-}
+
+package org.biomoby.client;
+
+import java.io.*;
+import java.util.*;
+
+import javax.xml.namespace.QName;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.rpc.Service;
+import javax.xml.transform.TransformerException;
+
+import org.apache.axis.client.Call;
+import org.apache.axis.message.MessageElement;
+import org.apache.xml.utils.PrefixResolver;
+import org.apache.xml.utils.PrefixResolverDefault;
+import org.apache.xpath.XPath;
+import org.apache.xpath.XPathContext;
+import org.apache.xpath.objects.XNodeSet;
+import org.apache.xpath.objects.XObject;
+
+import org.biomoby.shared.*;
+import org.biomoby.shared.data.*;
+import org.biomoby.shared.parser.MobyTags; // defined the Moby XML element names
+import org.biomoby.w3c.addressing.EndpointReference;
+import org.omg.lsae.notifications.AnalysisEvent;
+
+import org.w3c.dom.*;
+
+/**
+ * This class handles the WSDL transaction to request a response from a remote SOAP Web service that handles the <a
+ * href="http://www.biomoby.org">MOBY</a> format. It depends on having already retrieved the definition of the Web
+ * service via the MOBY central registry using the <a href="http://www.biomoby.org/moby-live/Java/docs/index.html">jMOBY</a>
+ * API, and for now it uses the <a href="http://ws.apache.org/axis/index.html">Apache Axis</a> Web services framework,
+ * as well as Apache Xalan. There are code comments for the few lines that rely on Axis classes rather than the JAX-RPC
+ * interfaces.
+ * 
+ * @author Paul Gordon gordonp at ucalgary.ca
+ */
+public class MobyRequest {
+
+	protected MobyService mobyService = null;
+	protected MobyContentInstance inputData = null;
+	protected MobyContentInstance outputData = null;
+	protected Central mobyCentral = null;
+	protected PrefixResolver mobyPrefixResolver = null;
+
+	protected Hashtable wsdlCache = null;
+	protected String lastWsdlCacheKey = null;
+	protected DocumentBuilder docBuilder = null;
+	protected Service service = null;
+
+	protected Class stringType;
+	protected static boolean debug = false;
+	protected PrintStream debugPS = System.err;
+	protected XPathContext xpath_context;
+	protected String responseString = null;
+
+	private XPath stringEncodedXPath;
+	private XPath base64EncodedXPath;
+	private XPath simpleChildXPath;
+	private XPath collectionChildXPath;
+
+	private int autoID = 0;
+
+	// Used as invocation callback if MobyRequest is acting as a server,
+	// or is executing services as a client asynchronously
+	private Vector< MobyRequestEventHandler > eventHandlers;
+
+	private String user;
+	private String password;
+
+	/**
+	 * Default constructor. You should have a Central instance around since you're going to be retrieving MobyServices
+	 * to pass into here. Lets reuse it.
+	 * 
+	 * @param central An instance of a Moby central object so we can make requests about object types, etc.
+	 * @throws ParserConfigurationException if JAXP doesn't have any valid DOM-building XML parsers set up for use
+	 */
+	public MobyRequest( Central central ) throws ParserConfigurationException {
+		mobyCentral = central;
+		wsdlCache = new Hashtable();
+
+		eventHandlers = new Vector< MobyRequestEventHandler >();
+
+		DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
+		dbf.setNamespaceAware( true );
+		docBuilder = dbf.newDocumentBuilder();
+
+		try {
+			stringType = Class.forName( "java.lang.String" );
+		}
+		catch ( ClassNotFoundException classe ) {
+			debugPS.println( "WARNING: Something is very wrong, could not find Class definition of String: " + classe );
+		}
+
+		xpath_context = new XPathContext();
+		mobyPrefixResolver = new MobyPrefixResolver();
+
+		// Now compile the XPath statements that will be used fetch data from the server response
+		try {
+			base64EncodedXPath = new XPath( "//*[starts-with(substring-after(@" + MobyPrefixResolver.XSI1999_PREFIX
+					+ ":type, ':'), \"base64\") or starts-with(substring-after(@" + MobyPrefixResolver.XSI2001_PREFIX
+					+ ":type, ':'), \"base64\")]", null, mobyPrefixResolver, XPath.SELECT );
+			stringEncodedXPath = new XPath( "//*[substring-after(@" + MobyPrefixResolver.XSI1999_PREFIX
+					+ ":type, ':')=\"string\" or substring-after(@" + MobyPrefixResolver.XSI2001_PREFIX
+					+ ":type, ':')=\"string\"] | //" + MobyPrefixResolver.SOAP_ENC_PREFIX + ":string", null,
+					mobyPrefixResolver, XPath.SELECT );
+			simpleChildXPath = new XPath( "moby:Simple | Simple", null, mobyPrefixResolver, XPath.SELECT );
+			collectionChildXPath = new XPath( "moby:Collection | Collection", null, mobyPrefixResolver, XPath.SELECT );
+		}
+		catch ( TransformerException te ) {
+			debugPS.println( "Syntax error encountered while compiling XPath "
+					+ "statements for internal use (code bug?): " + te );
+		}
+		setDebugMode( System.getProperty( "moby.debug" ) != null );
+	}
+
+	/**
+	 * @param mode if true, debugging information is printed to the stream returned by getDebugOutputStream
+	 */
+	public void setDebugMode(boolean mode) {
+		debug = mode;
+	}
+
+	/**
+	 * Standard error is used unless this method is called.
+	 * 
+	 * @param ps the OutputStream to which debugging information is sent.
+	 * @throws IllegalArgumentException if the stream is null
+	 */
+	public void setDebugPrintStream(PrintStream ps) throws IllegalArgumentException {
+		if ( ps == null ) {
+			throw new IllegalArgumentException( "The OutputStream specified to MobyRequest was null" );
+		}
+		debugPS = ps;
+	}
+
+	/**
+	 * @param user the user for a possible service authentication
+	 * @param password the passoword for a possible service authentication
+	 */
+	public void setAuthentication(String user, String password) {
+		this.user = user;
+		this.password = password;
+	}
+
+	/**
+	 * @return the instance of the class implementing Central that we are using
+	 */
+	public Central getCentralImpl() {
+		return mobyCentral;
+	}
+
+	/**
+	 * @param mobyservice the MobyService that should be executed when invokeService is called
+	 */
+	public void setService(MobyService mobyservice) {
+		if ( mobyservice == null ) {
+			mobyService = null;
+		}
+		else if ( mobyService == null || !mobyservice.equals( mobyService ) ) {
+			mobyService = mobyservice;
+		}
+	}
+
+	/**
+	 * @return the MobyService that will be executed when invokeService is called
+	 */
+	public MobyService getService() {
+		return mobyService;
+	}
+
+	/**
+	 * @return the Raw MOBY XML response as a string
+	 */
+	public String getResponseXML() {
+		return responseString;
+	}
+
+	/**
+	 * Sets the input data for the MOBY service request. The length of the input array, and the number of input
+	 * parameters required by the service must be equal when invokeService() is called. This method strictly enforces
+	 * that the input be of the appropriate type for the service.
+	 * 
+	 * Note that there is no requirement to use MobyDataInstance.setXmlMode() before passing in data, this class will
+	 * temporarily set the XML mode of the data when it is required.
+	 * 
+	 * @throws IllegalArgumentException if the input does not fit the criteria of the service (e.g. wrong data type)
+	 */
+	public void setInput(MobyContentInstance data) throws MobyException {
+		inputData = data;
+	}
+
+	/**
+	 * Takes the data in the array, with their current articleNames, as input for the service
+	 */
+	public void setInput(MobyDataInstance[] data) throws MobyException {
+		MobyDataJob job = new MobyDataJob();
+		for ( MobyDataInstance param : data ) {
+			job.put( param.getName(), param );
+		}
+		inputData = new MobyContentInstance();
+		inputData.put( job );
+	}
+
+	/**
+	 * Convenience method to run services that take one argument. If the service requires the input to have a name, it
+	 * will be automatically assigned.
+	 */
+	public void setInput(MobyDataInstance datum) throws MobyException {
+		setInput( datum, "" );
+	}
+
+	/**
+	 * Convenience method to run services that take one named argument.
+	 */
+	public void setInput(MobyDataInstance datum, String paramName) throws MobyException {
+		inputData = new MobyContentInstance( datum, paramName );
+	}
+
+	/**
+	 * @return the MobyService that will be executed when invokeService is called
+	 */
+	public MobyContentInstance getInput() {
+		return inputData;
+	}
+
+	/**
+	 * Same functionality as setSecondaryInput(MobyDataSecondaryInstance[])
+	 */
+	public void setSecondaryInput(Collection< MobyDataSecondaryInstance > secondaryData) throws MobyException {
+		setSecondaryInput( secondaryData.toArray( new MobyDataSecondaryInstance[ secondaryData.size() ] ) );
+	}
+
+	/**
+	 * This method will assign the provided secondary parameters to all primary input data currently in this object.
+	 * This is covenient if you are running 100 seqs through BLAST and only want to set the parameters once. If you
+	 * instead want to set secondary input differently for all primary inputs, you'll need to create a custom
+	 * MobyContentInstance as input to setInput().
+	 * 
+	 * @throws MobyException if a parameter name is blank, or overrides a primary parameter
+	 */
+	public void setSecondaryInput(MobyDataSecondaryInstance[] secondaryData) throws MobyException {
+
+		Iterator queryNames = inputData.keySet().iterator();
+		// For each query
+		while (queryNames.hasNext()) {
+			MobyDataJob queryParams = inputData.get( queryNames.next() );
+			// Set all the secondary params (overwrites any old ones)
+			for ( int i = 0; i < secondaryData.length; i++ ) {
+				String secName = secondaryData[ i ].getName();
+				if ( secName == null || secName.length() == 0 ) {
+					throw new MobyException( "A secondary parameter cannot have a blank name (array index " + i + ")" );
+				}
+				if ( queryParams.containsKey( secName ) && queryParams.get( secName ) instanceof MobyPrimaryData ) {
+					throw new MobyException( "A secondary parameter cannot override an existing primary parameter "
+							+ "with the same name (" + secName + ")" );
+				}
+				queryParams.put( secName, secondaryData[ i ] );
+			}
+		}
+	}
+
+	/**
+	 * @return a vector of MobyDataInstance[], each element of the vector is the collection of response objects for the
+	 *         correspondingly indexed input request.
+	 * 
+	 * @throws MobyException if you try to get the results before calling InvokeService
+	 */
+	public MobyContentInstance getOutput() throws MobyException {
+		if ( outputData == null ) {
+			throw new MobyException( "Trying to access MOBY service results " + "before the service is invoked" );
+		}
+		else {
+			return outputData;
+		}
+	}
+
+	/**
+	 * The main method of the class. If all of the MOBY input objects are properly defined according to the Web service
+	 * definition, a SOAP request will be sent to the remote server, and the method will return one or more MOBY objects
+	 * (synchronous). Call this method after calling setService, and setInput. If you do not call setSecondaryInput, the
+	 * default secondary parameter values will be used.
+	 * 
+	 * @return the results of the remote Web service in response to the give input
+	 * 
+	 * @throws MobyException i.e. there was something wrong with the input, output or remote service's logic
+	 * @throws SOAPException i.e. there was a problem with the underlying transaction/transport layer
+	 */
+	public MobyContentInstance invokeService() throws Exception, MobyException, SOAPException, NoSuccessException {
+		return mobyService.isAsynchronous()
+				? invokeService( inputData, new StringBuffer() )
+				: invokeService( inputData, ( StringBuffer ) null );
+	}
+
+	// Used internally for asynchronous thread calls that all need the XML data
+	// and can't rely on the answer from thread-insensitive getResponseXML()
+	private MobyContentInstance invokeService(MobyContentInstance inData, StringBuffer contentsXML) throws Exception,
+			MobyException, SOAPException, NoSuccessException {
+		return invokeService( inData, contentsXML, null, 0 );
+	}
+
+	private MobyContentInstance invokeService(MobyContentInstance inData, StringBuffer contentsXML,
+			MobyRequestEventHandler handler, int requestId) throws Exception, MobyException, SOAPException,
+			NoSuccessException {
+
+		if ( mobyService == null ) {
+			throw new MobyException( "Tried to invoke null service from MobyRequest (call setService first)" );
+		}
+
+		Element mobyDOM = null;
+		if ( mobyService.isAsynchronous() ) {
+			// Async is "simpler", because it had to merge DOMs together into a single MobyContentInstance anyway
+			MobyContentInstance mci = performAsyncSOAPRequest( mobyService, inData, handler, requestId );
+			StringWriter writer = new StringWriter();
+			MobyDataUtils.toXMLDocument( writer, mci );
+			contentsXML.append( writer.toString() );
+			return mci;
+		}
+		else {
+			String mobyXML = convertMOBYDataToMOBYRequest( inData );
+			Call call = getServiceFromWSDL();
+			if ( user != null && password != null ) {
+				call.setProperty( Call.USERNAME_PROPERTY, user );
+				call.setProperty( Call.PASSWORD_PROPERTY, password );
+			}
+			mobyDOM = performSOAPRequest( call, mobyXML, contentsXML );
+			// The following parses the DOM and extracts all the appropriate jMOBY objects to represent the XML in Java
+			return MobyDataUtils.fromXMLDocument( mobyDOM, mobyService.getServiceType().getRegistry() );
+		}
+	}
+
+	protected MobyContentInstance performAsyncSOAPRequest(MobyService mservice, MobyContentInstance inData,
+			MobyRequestEventHandler handler, int requestId) throws Exception {
+		String mobyXML = convertMOBYDataToMOBYRequest( inData );
+		EndpointReference epr = AsyncClient.sendRequest( mservice, mobyXML );
+
+		// Essentially cloning, so removing ids doesn't change the
+		// MobyContentInstance "data" (which we will use again later on)
+		MobyContentInstance finalContents = new MobyContentInstance();
+		Set< String > queryIDs = new HashSet< String >( inData.keySet() );
+		try {
+			// Should add some timeout here...
+			while (!queryIDs.isEmpty()) {
+				// todo: make this setable
+				Thread.sleep( 5000 );
+
+				AnalysisEvent[] events = AsyncClient.poll( epr, queryIDs );
+
+				Vector< String > newDataAvailable = new Vector< String >();
+				for ( AnalysisEvent event : events ) {
+					if ( event != null && event.isCompleted() ) {
+						queryIDs.remove( event.getQueryId() );
+						newDataAvailable.add( event.getQueryId() );
+					}
+				}
+
+				if ( newDataAvailable.size() > 0 ) {
+					// Parse and merge the new data into the existing contents
+					InputStream resultStream = AsyncClient.getResultStream( epr, newDataAvailable );
+					Element mobyDOM = asyncSoapTextToMobyDOM( resultStream );
+					MobyContentInstance newResults = MobyDataUtils.fromXMLDocument( mobyDOM,
+																					mservice.getServiceType()
+																							.getRegistry() );
+					// The merge
+					for ( String jobid : newResults.keySet() ) {
+						finalContents.put( jobid, newResults.get( jobid ) );
+					}
+
+					// Inform the handler that some data has been added to the response (for incremental display?)
+					if ( handler != null ) {
+						MobyRequestEvent mre = new MobyRequestEvent( finalContents, this, mservice, null, requestId );
+						StringWriter xmlWriter = new StringWriter();
+						MobyDataUtils.toXMLDocument( xmlWriter, finalContents );
+
+						mre.setContentsXML( xmlWriter.toString() );
+						if ( !queryIDs.isEmpty() ) {
+							// Send an update event only if we aren't finished yet.
+							// If we are finished, the client is going to get this event as the
+							// invocation thread finishes up (no need to double up).
+							handler.processEvent( mre );
+						}
+					}
+				}
+			}
+		}
+		catch ( Exception e ) {
+			e.printStackTrace();
+			AsyncClient.destroy( epr );
+			throw new Exception( "Exception occured while polling the service invocation: " + e );
+		}
+
+		return finalContents;
+	}
+
+	private Element asyncSoapTextToMobyDOM(InputStream inStream) throws Exception {
+		Element soapDOM = null;
+		synchronized ( docBuilder ) {
+			soapDOM = docBuilder.parse( inStream ).getDocumentElement();
+		}
+		final boolean IS_ASYNC_SERVICE_CALL = true;
+		return decodeSOAPMessage( soapDOM, null, null, IS_ASYNC_SERVICE_CALL );
+	}
+
+	/**
+	 * Asynchronous call to invokeService. A callback to the passed-in handler will be made when the response is ready,
+	 * or there is an exception.
+	 * 
+	 * @return the id that the callback event will return from getID(), allowing a client to distinguish between
+	 *         multiple concurrent invocation callbacks
+	 */
+	public synchronized int invokeService(MobyRequestEventHandler handler) {
+		int id = autoID++;
+
+		Thread t = new InvocationThread( this, inputData, handler, id ); // see internal class definition below
+		t.start();
+
+		return id;
+	}
+
+	// This is the class that asynchronously calls the service and does a callback to
+	// the handler specified in the invocation.
+	class InvocationThread extends Thread {
+		MobyContentInstance data;
+		MobyService mservice;
+		MobyRequest mobyRequest;
+		MobyRequestEventHandler handler;
+		int requestId;
+
+		InvocationThread( MobyRequest mr, MobyContentInstance inData, MobyRequestEventHandler h, int id ) {
+			data = inData;
+			mobyRequest = mr;
+			mservice = mobyRequest.getService();
+			handler = h;
+			requestId = id;
+
+			// Name the thread after the service being run, mostly for ease of debugging
+			setName( mservice.getName() + requestId );
+		}
+
+		public void run() {
+			MobyRequestEvent requestEvent = new MobyRequestEvent( data, mobyRequest, mservice, null, requestId );
+			// Tell the handler we're starting the request, with the given data
+			handler.start( requestEvent );
+
+			MobyRequestEvent responseEvent = null;
+			MobyContentInstance content = null;
+			StringBuffer contentsXML = new StringBuffer(); // to be filled in by the RPC call below
+			try {
+				content = mobyRequest.invokeService( data, contentsXML, handler, requestId ); // RPC call...
+			}
+			catch ( Exception e ) {
+				responseEvent = new MobyRequestEvent( content, mobyRequest, mservice, e, requestId );
+			}
+			catch ( Error err ) {
+				responseEvent = new MobyRequestEvent( content, mobyRequest, mservice, err, requestId );
+			}
+			if ( responseEvent == null ) {
+				responseEvent = new MobyRequestEvent( content, mobyRequest, mservice, null, requestId );
+			}
+			// We've got the raw XML laying around, so why not provide it unmolested to the callback?
+			responseEvent.setContentsXML( contentsXML.toString() );
+			handler.processEvent( responseEvent );
+			handler.stop( mobyRequest, requestId );
+		}
+	}
+
+	public void addEventHandler(MobyRequestEventHandler h) {
+		eventHandlers.add( h );
+	}
+
+	public void removeEventHandler(MobyRequestEventHandler h) {
+		eventHandlers.remove( h );
+	}
+
+	public void sendResponse(MobyRequestEvent mre) {
+		// Not yet implemented, need to conform to some web.xml specification here...
+	}
+
+	/**
+	 * This method retrieves from Moby Central a copy of the WSDL document for the service (or uses an internally cached
+	 * copy from a previous invocation), and sets the variables for the SOAP call appropriately so you can consequently
+	 * call performSOAPRequest.
+	 */
+	protected Call getServiceFromWSDL() throws MobyException, NoSuccessException {
+		// String wsdl = null;
+
+		// // Since this is how we retrieve a service from Central, use the same values as the key to the hash
+		// String wsdlCacheKey = mobyService.getName() + "@" + mobyService.getAuthority();
+
+		// // This is the same call as last time, so we don't need to change the setup
+		// if(wsdlCacheKey.equals(lastWsdlCacheKey)){
+		// return setCallFromWSDL((String) wsdlCache.get(wsdlCacheKey));
+		// }
+		// // We haven't encountered this service yet
+		// else if(!wsdlCache.containsKey(wsdlCacheKey)){
+		// wsdl = mobyCentral.getServiceWSDL(mobyService.getName(), mobyService.getAuthority());
+		// wsdlCache.put(wsdlCacheKey, wsdl);
+		// }
+		// // We've dealt with this one before
+		// else{
+		// wsdl = (String) wsdlCache.get(wsdlCacheKey);
+		// }
+
+		// lastWsdlCacheKey = wsdlCacheKey; // Keep track of the last invocation
+
+		// Get ready to do SOAP
+		return setCallFromWSDL( null );
+	}
+
+	/**
+	 * Creates the SOAP Call that will be invoked later. This should be based on the WSDL document and parameter
+	 * information from the MobyService, but these are currently not up to snuff.
+	 */
+	protected Call setCallFromWSDL(String wsdl) throws MobyException, SOAPException {
+		if ( service == null ) {
+			service = new org.apache.axis.client.Service(); // AXIS SPECIFIC This acts as a factory for Calls
+		}
+
+		Call soapCall;
+		try {
+			soapCall = ( Call ) service.createCall();// create a fresh Call each time
+		}
+		catch ( javax.xml.rpc.ServiceException se ) {
+			throw new SOAPException( "Could not instatiate call to SOAP Service: " + se );
+		}
+
+		// Should initialize endpoint, etc. This call is AXIS SPECIFIC, otherwise you'll
+		// have to do the call's info setting manually.
+		// ((org.apache.axis.client.Call) soapCall).setSOAPService(soapService);
+		soapCall.removeAllParameters();
+		soapCall.setTargetEndpointAddress( mobyService.getURL() );
+		soapCall.setPortName( new QName( "http://biomoby.org/", mobyService.getName() + "PortType" ) );
+		// soapCall.setOperationName(new QName("http://biomoby.org/",
+		// mobyService.getName()));
+		soapCall.setSOAPActionURI( "http://biomoby.org/#" + mobyService.getName() );
+		return soapCall;
+	}
+
+	/**
+	 * Calls the invoke() method of the JAX-RPC Call interface.
+	 */
+	protected Element performSOAPRequest(Call soapCall, String mobyInputXML, StringBuffer contentsXMLOutput)
+			throws SOAPException {
+		// First, turn the input objects into a MOBY XML request
+		String[] mobyXMLInputData = new String[ 1 ];
+
+		// Setup
+		mobyXMLInputData[ 0 ] = mobyInputXML;
+
+		if ( debug )
+			debugPS.println( "returnType just before invoke call is " + soapCall.getReturnType() );
+		Object returnedObject = null;
+		try {
+			returnedObject = soapCall.invoke( new QName( "http://biomoby.org/", mobyService.getName() ),
+												mobyXMLInputData );
+		}
+		catch ( Exception e ) {
+			e.printStackTrace();
+			// System.err.println("Input: "+mobyInputXML);
+			throw new SOAPException( "While invoking SOAP Call: " + e );
+		}
+
+		try {
+			if ( debug ) {
+				debugPS.println( "SOAP Response was:\n" );
+				debugPS.println( soapCall.getResponseMessage().getSOAPPart().getEnvelope() );
+			}
+			Element resultDom = ( ( MessageElement ) soapCall.getResponseMessage().getSOAPPart().getEnvelope() ).getAsDOM();
+			return decodeSOAPMessage( resultDom, contentsXMLOutput, mobyInputXML );
+		}
+		catch ( Exception e ) {
+			e.printStackTrace();
+			throw new SOAPException( "Could not get SOAP response as DOM Element: " + e );
+		}
+
+	}
+
+	public Element decodeSOAPMessage(Element n, StringBuffer contentsXMLOutput, String inputXML) throws SOAPException,
+			MobyException {
+		return decodeSOAPMessage( n, contentsXMLOutput, inputXML, false );
+	}
+
+	/**
+	 * Isolates the MOBY Data from the SOAP message returned by the remote service host.
+	 * 
+	 * @throws SOAPException if the MOBY payload cannot be found in the SOAP message
+	 * @throws MobyException if the MOBY message is not well-formed XML
+	 * 
+	 * @return The root element of the MOBY response DOM
+	 */
+	public Element decodeSOAPMessage(Element n, StringBuffer contentsXMLOutput, String inputXML, boolean async)
+			throws SOAPException, MobyException {
+		if ( n == null ) {
+			throw new SOAPException( "SOAP Message given to decode is null" );
+		}
+
+		NodeList node_list = null;
+		XPath responseElementXPath = null;
+		try {
+			if ( async ) {
+				responseElementXPath = new XPath( "//" + MobyPrefixResolver.WSRP_PREFIX + ":"
+						+ AsyncClient.WSRP_MULTI_PROPERTY_TAG_NAME + "Response", null, mobyPrefixResolver, XPath.SELECT );
+			}
+			else {
+				responseElementXPath = new XPath( "//" + MobyPrefixResolver.MOBY_TRANSPORT_PREFIX + ":"
+						+ mobyService.getName() + "Response | //" + mobyService.getName() + "Response | " + "//"
+						+ MobyPrefixResolver.MOBY_TRANSPORT_PREFIX + ":" + mobyService.getName() + " | //"
+						+ mobyService.getName(), null, mobyPrefixResolver, XPath.SELECT );
+			}
+		}
+		catch ( TransformerException te ) {
+			throw new SOAPException( "Cannot select SOAP nodes due to exception "
+					+ "while compiling XPath statement (code bug?):" + te );
+		}
+		try {
+			node_list = runXPath( responseElementXPath, n );
+		}
+		catch ( TransformerException te ) {
+			throw new SOAPException( "Cannot select SOAP nodes due to exception " + "while executing XPath statement:"
+					+ te );
+		}
+
+		if ( node_list == null || node_list.getLength() == 0 ) {
+			throw new SOAPException( "Could not find a response element in SOAP payload (service "
+					+ mobyService.getName() + ")" );
+		}
+
+		if ( node_list.getLength() > 1 ) {
+			throw new SOAPException( "Found more than one response element in SOAP payload, "
+					+ "unable to resolve ambiguity of the payload (service provider error?)" );
+		}
+
+		Node[] responseNodes = null;
+		if ( async ) {
+			Vector< Node > nodes = new Vector< Node >();
+			NodeList resultNodeList = node_list.item( 0 ).getChildNodes();
+			for ( int i = 0; resultNodeList != null && i < resultNodeList.getLength(); i++ ) {
+				if ( ! ( resultNodeList.item( i ) instanceof Element ) ) {
+					continue;
+				}
+				Element resultElement = ( Element ) resultNodeList.item( i );
+				if ( resultElement.getLocalName().startsWith( AsyncClient.MOBY_RESULT_PROPERTY_PREFIX ) ) {
+					nodes.add( resultElement );
+				}
+			}
+			responseNodes = nodes.toArray( new Node[ nodes.size() ] );
+		}
+		else {
+			responseNodes = new Node[]{node_list.item( 0 )};
+		}
+
+		Element domRoot = null; // Where the result will be put
+
+		for ( Node responseNode : responseNodes ) {
+			// Find base64 encoded elements in the SOAP message using XPath and
+			// replace them with the real decoded contents
+			node_list = null;
+			try {
+				node_list = runXPath( base64EncodedXPath, responseNode );
+			}
+			catch ( TransformerException te ) {
+				throw new SOAPException( "Cannot select base64 encoded SOAP nodes due to exception "
+						+ "while executing XPath statement:" + te );
+			}
+			if ( debug && node_list != null ) {
+				debugPS.println( "There were " + node_list.getLength() + " base64 encoded elements in the data" );
+			}
+
+			// Do decoding for each base64 part found
+			for ( int i = 0; node_list != null && i < node_list.getLength(); i++ ) {
+				org.w3c.dom.Node change = node_list.item( i );
+				/* Make sure the text data is all put into one contiguous piece for decoding */
+				change.normalize();
+
+				byte[] decodedBytes = org.apache.axis.encoding.Base64.decode( change.getFirstChild().getNodeValue() );
+				String newText = new String( decodedBytes );
+				if ( debug ) {
+					debugPS.println( "New decoded text is" + newText );
+				}
+
+				// Swap out this node for the decoded data
+				change.getParentNode().replaceChild( n.getOwnerDocument().createTextNode( new String( decodedBytes ) ),
+														change );
+			}
+
+			// Now see if there are any strings that need decoding
+			node_list = null;
+			try {
+				node_list = runXPath( stringEncodedXPath, responseNode );
+			}
+			catch ( TransformerException te ) {
+				throw new SOAPException( "Cannot select string encoded SOAP nodes due to exception "
+						+ "while executing XPath statement:" + te );
+			}
+
+			// Do concatenation for each plain string part found
+			for ( int i = 0; node_list != null && i < node_list.getLength(); i++ ) {
+				org.w3c.dom.Node change = node_list.item( i );
+				/* Make sure the text data is all put into one contiguous piece for decoding */
+				change.normalize();
+				String plainString = "";
+				int j = 0;
+				for ( NodeList children = change.getChildNodes(); children != null && j < children.getLength(); j++ ) {
+					Node child = children.item( j );
+					if ( child instanceof CDATASection || child instanceof Text ) {
+						plainString += child.getNodeValue();
+						if ( debug ) {
+							debugPS.println( "Plain string is now " + plainString );
+						}
+					}
+				}
+
+				// Swap out this node for the decoded data
+				change.getParentNode().replaceChild( n.getOwnerDocument().createCDATASection( plainString ), change );
+			}
+			if ( debug && node_list != null ) {
+				debugPS.println( "There were " + node_list.getLength()
+						+ " XML Schema string encoded elements in the data" );
+			}
+
+			// Parse the MOBY XML document payload
+			responseNode.normalize();
+			NodeList children = responseNode.getChildNodes();
+			if ( children == null ) {
+				throw new MobyException( "The MOBY payload has no contents at all" );
+			}
+			if ( children.getLength() != 1 ) {
+				if ( debug ) {
+					debugPS.println( "Warning: MOBY Payload appears to have more than "
+							+ "just text in it, skipping the non-text sections" );
+				}
+			}
+
+			Element predefinedDOM = null; // Choice of ripping DOM Element for moby payload out of SOAP DOM
+			String localResponseString = ""; // or storing raw XML strings, to be converted to a DOM later
+			for ( int j = 0; j < children.getLength(); j++ ) {
+				Node child = children.item( j );
+				if ( child instanceof CDATASection || child instanceof Text ) {
+					// Unescape XML special characters in the string, so we can later on
+					// parse the payload as regular XML.
+					// Ignore whitespace-only node
+					if ( child.getNodeValue().matches( "^\\s+$" ) ) {
+						continue;
+					}
+					if ( debug ) {
+						debugPS.println( "Concatenating text in response " + child.getNodeValue() );
+					}
+					localResponseString += child.getNodeValue();// .replaceAll("&lt;", "<").replaceAll("&gt;",
+					// ">").replaceAll("(&amp;|&#x46;)", "&");
+				}
+				if ( child instanceof Element && child.getLocalName().equals( MobyTags.MOBY ) ) {
+					debugPS.println( "Warning: The MOBY contents was found as raw XML inside the SOAP response!\n"
+							+ "This is illegal according to the MOBY-API, please inform the service\n "
+							+ " provider, as parsing such text may not be supported in the future" );
+					localResponseString = null;
+					// Store the moby payload root element's DOM represntation, so we don't
+					// have to serialize it to the localResponseString and then parse it out
+					// again (that would be wasteful).
+					predefinedDOM = ( Element ) child;
+					break;
+				}
+			}
+
+			if ( localResponseString != null ) {
+				if ( localResponseString.length() == 0 ) {
+					throw new MobyException( "The MOBY payload has no text contents at all" );
+				}
+				if ( Character.isWhitespace( localResponseString.charAt( 0 ) ) ) {
+					localResponseString = localResponseString.trim();
+				}
+			}
+
+			// Check if the payload is an XML document. If not, try a last ditch effort
+			// by base64 decoding the contents. This is technically not allowable in the
+			// MOBY spec, but we are being lenient.
+			if ( localResponseString != null && !localResponseString.startsWith( "<?xml" ) ) {
+				// Is the XML declaration missing?
+				if ( localResponseString.startsWith( "<moby:MOBY" ) || localResponseString.startsWith( "<MOBY" ) ) {
+					localResponseString = "<?xml version=\"1.0\"?>\n" + localResponseString;
+					debugPS.println( "Warning: The MOBY contents was missing an XML declaration, but it is "
+							+ "required by the MOBY API, and may stop working in the future without it.  Please "
+							+ "contact the client's provider to correct this." );
+				}
+				else {
+					String oldResponse = localResponseString;
+					localResponseString = new String( org.apache.axis.encoding.Base64.decode( localResponseString ) );
+					if ( !localResponseString.startsWith( "<?xml" ) ) {
+						throw new MobyException( "The SOAP payload defining the MOBY contents "
+								+ "does not start with the xml processing instruction, and is therefore not "
+								+ "an XML document, as specified in the MOBY API. "
+								+ "Please contact the service provider.  Contents was: " + oldResponse );
+					}
+					debugPS.println( "Warning: The MOBY contents was needlessly base64 encoded (the SOAP "
+							+ "envelope does this for you).  It has been decoded, but this is not "
+							+ "part of the MOBY API, and may stop working in the future.  Please "
+							+ "contact the service provider to correct this." );
+				}
+			}
+
+			// A bit of a hack: most MOBY data represented in string form
+			// should have its formatting preserved (e.g. BLAST report), yet
+			// no-one uses the xml:space="preserve" attribute. This causes problems
+			// later on because once the document is parsed, there is no way to get the
+			// spaces back! I set the attribute explicitly at the top level of each data
+			// element to compensate. Unless of course this was already done.
+			if ( localResponseString != null && localResponseString.indexOf( "xml:space=\"preserve\"" ) == -1 ) {
+				localResponseString = localResponseString.replaceAll( "<String", "<String xml:space=\"preserve\"" );
+			}
+
+			try {
+				if ( async && domRoot != null ) {
+					// We will actually be appending several full MOBY messages together, so
+					// we need to do a DOM-meld at the mobyData tag level.
+					Element newContentsTag = null;
+					if ( predefinedDOM != null ) { // we have the MOBY element as a DOM Element already from the SOAP
+						// DOM
+						newContentsTag = MobyPrefixResolver.getChildElement( predefinedDOM, MobyTags.MOBYCONTENT );
+					}
+					else {
+						synchronized ( docBuilder ) {
+							Document newDoc = docBuilder.parse( new ByteArrayInputStream(
+									localResponseString.getBytes() ) );
+							newContentsTag = MobyPrefixResolver.getChildElement( newDoc.getDocumentElement(),
+																					MobyTags.MOBYCONTENT );
+						}
+					}
+					// Find the mobyContents tag under the root MOBY tag...
+					Element existingContentsTag = MobyPrefixResolver.getChildElement( domRoot, MobyTags.MOBYCONTENT );
+					NodeList newJobTags = newContentsTag.getChildNodes();
+					for ( int i = 0; newJobTags != null && i < newJobTags.getLength(); i++ ) {
+						// Service notes blocks must be merged
+						if ( newJobTags.item( i ) instanceof Element
+								&& newJobTags.item( i ).getLocalName().equals( MobyTags.SERVICENOTES ) ) {
+							Element existingServiceNotes = MobyPrefixResolver.getChildElement( existingContentsTag,
+																								MobyTags.SERVICENOTES );
+							if ( existingServiceNotes == null ) {
+								existingContentsTag.appendChild( newJobTags.item( i ) );
+							}
+							else {
+								NodeList newServiceData = newJobTags.item( i ).getChildNodes();
+								for ( int j = 0; newServiceData != null && j < newServiceData.getLength(); j++ ) {
+									existingServiceNotes.appendChild( newServiceData.item( j ) );
+								}
+							}
+						}
+						else { // everything else is at the same level (i.e. mobyData blocks)
+							existingContentsTag.appendChild( newJobTags.item( i ) );
+						}
+					}
+				}
+				else { // synchronous service call, or first async call (which needs to create a doc anyway)
+					// Synchronized to avoid Xerces exception FWK005: concurrent parsing is disallowed
+					if ( predefinedDOM != null ) { // we have the MOBY element as a DOM Element already from the SOAP
+						// DOM
+						domRoot = predefinedDOM;
+					}
+					else {
+						synchronized ( docBuilder ) {
+							domRoot = docBuilder.parse( new ByteArrayInputStream( localResponseString.getBytes() ) )
+												.getDocumentElement();
+						}
+					}
+				}
+			}
+			catch ( org.xml.sax.SAXException saxe ) {
+				throw new MobyException( "The SOAP payload defining the MOBY Result " + "could not be parsed: " + saxe );
+			}
+			catch ( java.io.IOException ioe ) {
+				throw new MobyException( "The SOAP payload defining the MOBY Result "
+						+ " could not be read (from a String!)" + ioe );
+			}
+
+			// Now, either save the xml we got in the class instance member (for synchronous calls to MobyRequest)
+			// or in the StringBuffer given as a parameter to this function (async calls)
+			if ( contentsXMLOutput != null ) {
+				contentsXMLOutput.append( localResponseString );
+			}
+			else {
+				responseString = localResponseString;
+			}
+		} // end for responseNode in responseNodes
+
+		// release resources related to the Xpath execution, since we won't be using this doc anymore
+		releaseXPath( n );
+
+		return domRoot;
+	}
+
+	public String convertMOBYDataToMOBYRequest(MobyDataInstance data) throws MobyException {
+		return convertMOBYDataToMOBYRequest( new MobyContentInstance( data, "" ) );
+	}
+
+	/**
+	 * Creates an XML representation of the data, renamed to fit the needs of the service if necessary, and adding any
+	 * secondary parameter default values if not already specified in the incoming data.
+	 * 
+	 * @param data the array of input parameters to put in a MOBY XML request
+	 * 
+	 * @return the XML representation of the input data
+	 */
+	public String convertMOBYDataToMOBYRequest(MobyContentInstance data) throws MobyException {
+
+		MobyData[] inputs = mobyService.getPrimaryInputs();
+		MobySecondaryData[] secondaries = mobyService.getSecondaryInputs();
+
+		// Make sure the number of input args is correct for each query being submitted
+		for ( Map.Entry< String, MobyDataJob > entry : data.entrySet() ) {
+			String queryName = entry.getKey();
+			MobyDataJob query = entry.getValue();
+
+			// Additionally, we check if they are MobyDataInstances below
+			Map< String, MobyPrimaryData > primaryParams = new HashMap< String, MobyPrimaryData >();
+			Map< String, MobySecondaryData > secondaryParams = new HashMap< String, MobySecondaryData >();
+
+			// To store the primary input parameter name as given by the user,
+			// in case we need it later on for parameter renaming...
+			String primaryParamName = null;
+
+			for ( Map.Entry< String, MobyDataInstance > subentry : query.entrySet() ) {
+				String name = subentry.getKey();
+				MobyDataInstance param = subentry.getValue();
+				if ( param == null ) {
+					throw new MobyException( "Query " + queryName + " contained a null input parameter (" + name + ")" );
+				}
+				else if ( param instanceof MobyPrimaryData ) {
+					primaryParams.put( name, ( MobyPrimaryData ) param );
+					primaryParamName = name;
+				}
+				else if ( param instanceof MobySecondaryData ) {
+					secondaryParams.put( name, ( MobySecondaryData ) param );
+				}
+				else {
+					System.err.println( "Input parameter " + name + " (query " + queryName
+							+ ") was not a MobyPrimaryData or MobySecondaryData "
+							+ "as expected, but rather was of class " + param.getClass().getName() );
+				}
+			}
+
+			if ( inputs != null && inputs.length != primaryParams.size() ) {
+				throw new MobyException( "Service " + mobyService.getName() + " was provided " + primaryParams.size()
+						+ " primary input parameter(s), but takes " + inputs.length + " (query " + queryName + ")" );
+			}
+			if ( secondaries != null ) {
+				// If no secondaries provided, fill them in by default
+				if ( secondaries.length != 0 ) {
+					for ( MobySecondaryData secondary : secondaries ) {
+						if ( !secondaryParams.containsKey( secondary.getName() ) ) {
+							if ( debug ) {
+								System.err.println( "Setting default secondary param value for missing param "
+										+ secondary );
+							}
+							query.put( secondary.getName(), new MobyDataSecondaryInstance( secondary ) );
+						}
+					}
+				}
+				if ( secondaries.length != secondaryParams.size() ) {
+					throw new MobyException( "Service " + mobyService.getName() + " was provided "
+							+ secondaryParams.size() + " secondary input parameter(s), but takes " + secondaries.length
+							+ " (query " + queryName + ").  Extra secondary" + " parameters must have been specified" );
+				}
+			}
+
+			// If there was one anonymous input, assign the name automatically in
+			// the case the service requires it to be named. This is the only
+			// unambiguous case in which we can do this.
+			if ( inputs.length == 1 ) {
+				String serviceParamName = inputs[ 0 ].getName(); // name as req'd by the service
+
+				// name isn't the same as required currently
+				if ( serviceParamName != null && serviceParamName.length() > 0
+						&& !serviceParamName.equals( primaryParamName ) ) {
+					// take out the old parameter
+					MobyPrimaryData theInputToRename = ( MobyPrimaryData ) query.remove( primaryParamName );
+
+					// Add in the same parameter, but with the appropriate name
+					query.put( serviceParamName, ( MobyDataInstance ) theInputToRename );
+				}
+			}
+		}
+
+		ByteArrayOutputStream mobyRequest = new ByteArrayOutputStream();
+		try {
+			MobyDataUtils.toXMLDocument( mobyRequest, data );
+		}
+		catch ( MobyException me ) {
+			throw me;
+		}
+		catch ( Exception e ) {
+			e.printStackTrace();
+			throw new MobyException( "Could not create MOBY payload XML from input data: " + e );
+		}
+
+		if ( debug ) {
+			debugPS.println( "Input to MOBY Service is:" );
+			debugPS.print( mobyRequest.toString() );
+		}
+
+		return mobyRequest.toString();
+	}
+
+	/**
+	 * A method that sets up the execution environment for and runs a compiled XPath statement against a DOM node You
+	 * should call releaseXPath when you're done with the results
+	 * 
+	 * @return the list of Nodes that satisfy the XPath in this Node's context
+	 */
+	protected NodeList runXPath(XPath xpath, Node n) throws TransformerException {
+		NodeList result = null;
+		int dtm_node_handle = xpath_context.getDTMHandleFromNode( n );
+		PrefixResolver node_prefix_resolver = new PrefixResolverDefault( n );
+		XObject xobject = xpath.execute( xpath_context, n, node_prefix_resolver );
+		if ( xobject instanceof XNodeSet ) {
+			result = ( ( XNodeSet ) xobject ).nodelist();
+		}
+		else if ( debug && xobject != null ) {
+			debugPS.println( "Output of XPath was not a XNodeSet as expected, found " + xobject.getClass().getName() );
+			debugPS.flush();
+		}
+		return result;
+	}
+
+	protected void releaseXPath(Node n) {
+		xpath_context.release( xpath_context.getDTM( xpath_context.getDTMHandleFromNode( n ) ), false );
+	}
+}

===================================================================
RCS file: /home/repository/moby/moby-live/Java/src/main/org/biomoby/client/CentralImpl.java,v
retrieving revision 1.57
retrieving revision 1.58
diff -u -r1.57 -r1.58
--- /home/repository/moby/moby-live/Java/src/main/org/biomoby/client/CentralImpl.java	2008/10/30 02:33:25	1.57
+++ /home/repository/moby/moby-live/Java/src/main/org/biomoby/client/CentralImpl.java	2008/11/26 08:53:43	1.58
@@ -1,2139 +1,1957 @@
-// CentralImpl.java
-//    A default client to the Moby Central service.
-//
-//    senger at ebi.ac.uk
-//    February 2003
-//
-
-package org.biomoby.client;
-
-import org.biomoby.registry.meta.Registry;
-import org.biomoby.shared.Central;
-import org.biomoby.shared.MobyData;
-import org.biomoby.shared.MobyDataType;
-import org.biomoby.shared.MobyException;
-import org.biomoby.shared.MobyNamespace;
-import org.biomoby.shared.MobyPrimaryDataSet;
-import org.biomoby.shared.MobyPrimaryDataSimple;
-import org.biomoby.shared.MobyRelationship;
-import org.biomoby.shared.MobySecondaryData;
-import org.biomoby.shared.MobyService;
-import org.biomoby.shared.MobyServiceType;
-import org.biomoby.shared.NoSuccessException;
-import org.biomoby.shared.PendingCurationException;
-import org.biomoby.shared.MobyResourceRef;
-import org.biomoby.shared.Utils;
-
-import javax.xml.parsers.DocumentBuilderFactory;
-import javax.xml.parsers.DocumentBuilder;
-import javax.xml.namespace.QName;
-import org.w3c.dom.Document;
-import org.w3c.dom.Element;
-import org.w3c.dom.NamedNodeMap;
-import org.w3c.dom.Node;
-import org.w3c.dom.NodeList;
-
-import org.apache.axis.AxisFault;
-import org.apache.axis.client.Call;
-import org.apache.axis.client.Service;
-import org.apache.commons.httpclient.DefaultHttpMethodRetryHandler;
-import org.apache.commons.httpclient.Header;
-import org.apache.commons.httpclient.HttpClient;
-import org.apache.commons.httpclient.HttpException;
-import org.apache.commons.httpclient.HttpStatus;
-import org.apache.commons.httpclient.methods.HeadMethod;
-import org.apache.commons.httpclient.params.HttpMethodParams;
-
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.io.LineNumberReader;
-import java.io.PrintStream;
-import java.io.InputStream;
-import java.lang.reflect.Constructor;
-import java.net.HttpURLConnection;
-import java.net.MalformedURLException;
-import java.net.URL;
-import java.util.HashMap;
-import java.util.Hashtable;
-import java.util.Iterator;
-import java.util.Map;
-import java.util.Properties;
-import java.util.Vector;
-import java.util.TreeMap;
-import java.util.Comparator;
-import java.util.zip.GZIPInputStream;
-import java.util.logging.*;
-
-/**
- * A default implementation of the
- * interface {@link org.biomoby.shared.Central Central}
- * allowing access to a Moby registry.
- *<p>
- * This class is supposed to be used by all other clients that wish
- * to communicate with the Moby Registry, but do not want to know
- * about all XML details that are necessary for talking with the Moby Central
- * directly. This is an example of a client program:
- *<pre>
- * import org.biomoby.shared.Central;
- * import org.biomoby.shared.MobyException;
- * import org.biomoby.client.CentralImpl;
- * import java.util.Map;
- * import java.util.Iterator;
- *
- * public class Test {
- *
- *    public static void main (String[] args)
- *       throws MobyException {
- *
- *       Central worker = new CentralImpl();
- *       Map authorities = worker.getServiceNamesByAuthority();
- *
- *       for (Iterator it = authorities.entrySet().iterator(); it.hasNext(); ) {
- *          Map.Entry entry = (Map.Entry)it.next();
- *          System.out.println (entry.getKey());
- *          String[] names = (String[])entry.getValue();
- *          for (int i = 0; i < names.length; i++)
- *             System.out.println ("\t" + names[i]);
- *       }
- *    }
- * }
- *</pre>
- *
- * @author <A HREF="mailto:senger at ebi.ac.uk">Martin Senger</A>
- * @version $Id$
- */
-
-public class CentralImpl
-    implements Central, SimpleCache {
-
-    private URL endpoint;
-    private String uri;
-    protected boolean debug = false;
-
-    /** Common central used to if getDefaultCentral() is called */
-    protected static Map<String,CentralImpl> defaultCentrals = new HashMap<String,CentralImpl>();
-
-    /** Default location (endpoint) of a Moby registry. */
-    public static final String DEFAULT_ENDPOINT = "http://moby.ucalgary.ca/moby/MOBY-Central.pl";
-
-    /** Default namespace used by the contacted Moby registry. */
-    public static final String DEFAULT_NAMESPACE = "http://moby.ucalgary.ca/MOBY/Central";
-
-    /**
-     * The META-INF resource file that will be checked to determine what
-     * default class should be instantiated in order to create a Central Implementation     
-     * when getDefaultCentral() is called.
-     */
-    public static final String CENTRAL_IMPL_RESOURCE_NAME = "org.biomoby.shared.CentralDefaultImpl";
-    /** The class to use for getDefaultCentral if all else fails */
-    public static final String DEFAULT_CENTRAL_IMPL_CLASSNAME = "org.biomoby.client.CentralDigestCachedImpl";
-    private static Logger logger = Logger.getLogger("org.biomoby.client.CentralImpl");
-
-   /**
-    * Thread local that gives each thread its own
-    * DocumentBuilderFactory (since it is not thread-safe). Code taken
-    * from Apache's JaxpUtils.
-    */
-   public static ThreadLocal DOCUMENT_BUILDER_FACTORIES = new ThreadLocal() {
-	   protected synchronized Object initialValue() {
-	       DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
-	       dbf.setNamespaceAware (true);
-	       return dbf;
-	   }
-       };
-
-
-    /*************************************************************************
-     * Default constructor. It connects to a default Moby registry
-     * (as defined in {@link #DEFAULT_ENDPOINT}) using a default namespace
-     * (as defined int {@link #DEFAULT_NAMESPACE}).
-     *************************************************************************/
-    public CentralImpl()
-	throws MobyException {
-	this (DEFAULT_ENDPOINT, DEFAULT_NAMESPACE);
-    }
-
-    /*************************************************************************
-     * Constructor allowing to specify which Moby Registry to use.
-     *
-     * @throws MobyException if 'endpoint' is not a valid URL, or if no
-     *                          DOM parser is available
-     *************************************************************************/
-    public CentralImpl (String endpoint)
-	throws MobyException {
-	this (endpoint, DEFAULT_NAMESPACE);
-    }
-
-    /*************************************************************************
-     * Constructor allowing to specify which Moby Registry and what
-     * namespace to use. If any of the parameters is null, its default
-     * value is used instead.
-     *<p>
-     * @throws MobyException if 'endpoint' is not a valid URL, or if no
-     *                          DOM parser was found
-     *************************************************************************/
-    public CentralImpl (String endpoint, String namespace)
-	throws MobyException {
-
-	if (endpoint == null || "".equals (endpoint.trim()))
-	    endpoint = DEFAULT_ENDPOINT;
-	if (namespace == null || "".equals (namespace.trim()))
-	    namespace = DEFAULT_NAMESPACE;
-
-	try {
-	    this.endpoint = new URL (endpoint);
-	} catch (MalformedURLException e) {
-	    throw new MobyException ("Bad URL: " + endpoint);
-	}
-	this.uri = namespace;
-
-	cache = new Hashtable<String,Object>();
-	useCache = true;
-    }
-
-    /*************************************************************************
-     * Loads a DOM Document from an InputStream. Uses thread-safe
-     * mechanism.
-     *************************************************************************/
-    public static Document loadDocument (InputStream input)
-	throws MobyException {
-	try {
-	    DocumentBuilderFactory dbf
-		= (DocumentBuilderFactory)DOCUMENT_BUILDER_FACTORIES.get();
-	    DocumentBuilder db = dbf.newDocumentBuilder();
-	    return (db.parse (input));
-	} catch (Exception e) {
-	    throw new MobyException ("Problem with reading XML input: " + e.toString(), e);
-	}
-    }
-
-    /*************************************************************************
-     * Call 'method' with 'parameters' and return its result.
-     *************************************************************************/
-    protected Object doCall (String method, Object[] parameters)
-	throws MobyException {
-
-	Call call = null;
-	try {
-	    Service service = new Service();
-	    call = (Call) service.createCall();
-	    call.setTargetEndpointAddress (endpoint);
-	    call.setTimeout (new Integer (0));
-
-	    call.setSOAPActionURI (uri + "#" + method);
-
-	    if (debug) {
-		System.err.println ("METHOD CALL: " + method);
-		System.err.println ("------------");
-		if (parameters.length > 0)
-		    System.err.println (parameters[0] + "\n");
-		System.err.println ("------------\n");
-
-		Object result = call.invoke (uri, method, parameters);
-
-		System.err.println ("METHOD RETURN:");
-		System.err.println ("------------");
-		if (result != null)
-		    System.err.println (result + "\n");
-		System.err.println ("------------\n");
-
-		return resultToString (result);
-
-	    } else {
-		return resultToString (call.invoke (uri, method, parameters));
-	    }
-
-	} catch (AxisFault e) {
-	    throw new MobyException
-		(formatFault (e,
-			      endpoint.toString(),
-			      (call == null ? null : call.getOperationName())),
-		 e);
-// 		(endpoint.toString()+(call == null ? "" : call.getOperationName()), e);
-
-	} catch (Exception e) {
-	    throw new MobyException (e.toString(), e);
-// 	    e.printStackTrace();
- 	}
-    }
-
-
-    /**************************************************************************
-     * Parse the given XML sniplet to find tag 'success'. If it has value '1'
-     * look further for tag 'id' and return it back (or return an empty string
-     * if ID is not there). Otherwise raise an exception with the 'culprit'
-     * and with the message from the tag 'message'. <p>
-     *
-     * The return value is a two-element long array. The first element
-     * is the ID (given by BioMobe registry), and the second element
-     * is RDF corresponding with the registered object (BioMoby
-     * returns this only for service instances, so for other objects
-     * this will be null). <p>
-     *
-     * This is how the XML is supposed to look:
-     *     <MOBYRegistration>
-     *        <success> <!-- 1 | 0 | -1 --> </success>
-     *        <id> <!-- some id number for your registration --> </id>  
-     *        <message> <![CDATA[message here]]> </message>
-     *        <RDF> <!-- RDF of your service instance here (if applicable) --> </RDF>
-     *     </MOBYRegistration>
-     *
-     * Success takes the value "1" to indicate success, "0" to
-     * indicate failure, and "-1" to indicate "Pending Curation".
-     *************************************************************************/
-    protected String[] checkRegistration (String xml, Object culprit)
-	throws MobyException, NoSuccessException, PendingCurationException {
-
-	String id = "", success = "0", message = "", rdf = "";
-
-	// parse returned XML
-	Document document = loadDocument (new ByteArrayInputStream (xml.getBytes()));
-	Element root = document.getDocumentElement();
-
-	NodeList children = root.getChildNodes();
-	for (int i = 0; i < children.getLength(); i++) {
-	    if (children.item (i).getNodeType() != Node.ELEMENT_NODE)
-		continue;
-	    Element elem = (Element)children.item (i);
-	    if (elem.getNodeName().equals ("id")) {
-		if (elem.getFirstChild() != null)
-		    id = elem.getFirstChild().getNodeValue();
-	    } else if (elem.getNodeName().equals("success")) {
-		if (elem.getFirstChild() != null)
-		    success = elem.getFirstChild().getNodeValue();
-	    } else if (elem.getNodeName().equals ("message")) {
-		if (elem.getFirstChild() != null)
-		    message = elem.getFirstChild().getNodeValue();
-	    } else if (elem.getNodeName().equals ("RDF")) {
-		if (elem.getFirstChild() != null)
-		    rdf = elem.getFirstChild().getNodeValue();
-	    }
-	}
-
-	if (success.equals ("0"))
-	    throw new NoSuccessException (message, culprit);
-	else if (success.equals ("-1"))
-	    throw new PendingCurationException();
-	return new String[] { id, rdf };
-    }
-
-    /**************************************************************************
-     * Return a piece of XML created from the definitions representing input
-     * data types and their usage in the given service. Only data considered
-     * primary are included. Note that the main job of converting to XML is
-     * done by instances of MobyPrimaryData.
-     *
-     * The returned XML looks like this:
-     *    <Input>
-     *       <!-- zero or more Primary (Simple and/or Complex) articles -->
-     *    </Input>
-     *************************************************************************/
-    protected String buildPrimaryInputTag (MobyService service) {
-	StringBuffer buf = new StringBuffer();
-	MobyData[] primaryInputs = service.getPrimaryInputs();
-	buf.append ("<Input>\n");
-	for (int i = 0; i < primaryInputs.length; i++)
-	    buf.append (primaryInputs[i].toXML());
-	buf.append ("</Input>\n");
-	return new String (buf);
-    }
-
-    /**************************************************************************
-     * Return a piece of XML created from the definitions representing input
-     * data types and their usage in the given service. Only data considered
-     * secondary are included. Note that the main job of converting to XML is
-     * done by instances of MobySecondaryData.
-     *
-     * The returned XML looks like this:
-     *    <secondaryArticles>
-     *       <!-- zero or more INPUT Secondary articles -->
-     *    </secondaryArticles>
-     *************************************************************************/
-    protected String buildSecondaryInputTag (MobyService service) {
-	StringBuffer buf = new StringBuffer();
-	MobyData[] secInputs = service.getSecondaryInputs();
-	buf.append ("<secondaryArticles>\n");
-	for (int i = 0; i < secInputs.length; i++) {
-	    buf.append (secInputs[i].toXML());
-	}
-	buf.append ("</secondaryArticles>\n");
-	return new String (buf);
-    }
-
-    /**************************************************************************
-     * Return a piece of XML created from the definitions representing output
-     * data types and their usage in the given service. Only data considered
-     * primary are included. Note that the main job of converting to XML is
-     * done by instances of MobyPrimaryData.
-     *
-     * The returned XML looks like this:
-     *    <Output>
-     *       <!-- zero or more Primary (Simple and/or Complex) articles --> 
-     *    </Output>
-     *
-     *************************************************************************/
-    protected String buildOutputTag (MobyService service) {
-	StringBuffer buf = new StringBuffer();
-	MobyData[] primaryOutputs = service.getPrimaryOutputs();
-	buf.append ("<Output>\n");
-	for (int i = 0; i < primaryOutputs.length; i++)
-	    buf.append (primaryOutputs[i].toXML());
-	buf.append ("</Output>\n");
-	return new String (buf);
-    }
-
-    /**************************************************************************
-     * Return a piece of XML represented a query object (an object used
-     * to find a service).
-     *
-     * The returned XML looks like this:
-     *
-     *    <inputObjects>
-     *      <Input>
-     *           <!-- one or more Simple or Complex Primary articles -->
-     *      </Input>
-     *    </inputObjects>
-     *    <outputObjects>
-     *      <Output>
-     *           <!-- one or more Simple or Complex Primary articles -->
-     *      </Output>
-     *    </outputObjects>
-     *    <serviceType>ServiceTypeTerm</serviceType>
-     *    <serviceName>ServiceName</serviceName>
-     *    <Category>moby</Category>
-     *    <authURI>http://desired.service.provider</authURI>;
-     *    <expandObjects>1|0</expandObjects> 
-     *    <expandServices>1|0</expandServices>
-     *    <authoritative>1|0</authoritative>
-     *    <keywords>
-     *         <keyword>something</keyword>
-     *         ....
-     *         ....
-     *    </keywords>
-     *************************************************************************/
-    protected String buildQueryObject (MobyService service,
-				       String[] keywords,
-				       boolean expandObjects,
-				       boolean expandServices,
-				       boolean authoritative) {
-	if (service == null) {
-	    service = new MobyService ("dummy");
-	    service.setCategory ("");
-	}
-	StringBuffer buf = new StringBuffer();
-
-	buf.append ("<inputObjects>\n<Input>\n");
-	MobyData[] pi = service.getPrimaryInputs();
-	if (pi.length > 0) {
-	    for (int i = 0; i < pi.length; i++)
-		buf.append (pi[i].toXML());
-	}
-	buf.append ("</Input>\n</inputObjects>\n");
-
-	buf.append ("<outputObjects>\n<Output>\n");
-	MobyData[] po = service.getPrimaryOutputs();
-	if (po.length > 0) {
-	    for (int i = 0; i < po.length; i++)
-		buf.append (po[i].toXML());
-	}
-	buf.append ("</Output>\n</outputObjects>\n");
-
-	buf.append ("<serviceType>" + service.getType() + "</serviceType>\n");
-
-	String name = service.getName();
-	if (!name.equals ("") && !name.equals ("dummy") && !name.equals (MobyService.DUMMY_NAME))
-	    buf.append ("<serviceName>" + service.getName() + "</serviceName>\n");
-
-	String sigURL = service.getSignatureURL();
-	if (!sigURL.equals (""))
-	    buf.append ("<signatureURL>" + sigURL + "</signatureURL>\n");
-	
-	buf.append ("<Category>" + service.getCategory() + "</Category>\n");
-	buf.append ("<authURI>" + service.getAuthority() + "</authURI>\n");
-
-	buf.append ("<expandObjects>");
-	buf.append (expandObjects ? "1" : "0");
-	buf.append ("</expandObjects>\n");
-
-	buf.append ("<expandServices>");
-	buf.append (expandServices ? "1" : "0");
-	buf.append ("</expandServices>\n");
-
-	buf.append ("<authoritative>");
- 	buf.append (authoritative ? "1" : "0");
-	buf.append ("</authoritative>\n");
-
-	buf.append ("<keywords>\n");
-	if (keywords != null && keywords.length > 0) {
-	    for (int i = 0; i < keywords.length; i++) {
-		buf.append ("<keyword>");
-		buf.append (keywords[i]);
-		buf.append ("</keyword>\n");
-	    }
-	}
-	buf.append ("</keywords>\n");
-
-	return new String (buf);
-    }
- 
-    /**************************************************************************
-     * Extract one or more MobyService objects from the given XML piece.
-     * The XML should look like this:
-     * <pre>
-     *  &lt;Services&gt;
-     *    &lt;Service authURI="authority.URI.here" lsid="..." serviceName="MyService"&gt;
-     *      &lt;serviceType&gt;Service_Ontology_Term&lt;/serviceType&gt;
-     *      &lt;Category&gt;moby&lt;/Category&gt; &lt;!-- or 'cgi' or 'soap' --&gt;
-     *      &lt;contactEmail&gt;your at email.addy.here&lt;/contactEmail&gt;
-     *      &lt;signatureURL&gt;http://service.RDF.here&lt;/signatureURL&gt;
-     *      &lt;URL&gt;http://service.endpoint.here/scriptname&lt;/URL&gt;
-     *      &lt;authoritative&gt;1&lt;/authoritative&gt;
-     *      &lt;Input&gt;
-     *           &lt;!-- one or more Simple and/or Complex Primary articles --&gt;
-     *      &lt;/Input&gt;
-     *      &lt;Output&gt;
-     *           &lt;!-- one or more Simple and/or Complex Primary articles --&gt; 
-     *      &lt;/Output&gt;
-     *      &lt;secondaryArticles&gt;
-     *           &lt;!-- one or more Secondary articles --&gt;
-     *      &lt;/secondaryArticles&gt;
-     *      &lt;Description&gt;&lt;![CDATA[free text description here]]&gt;&lt;/Description&gt;
-     *    &lt;/Service&gt;
-     *    ...  &lt;!--  one or more Service blocks may be returned --&gt;
-     *    ...
-     *    ...
-     *  &lt;/Services&gt;
-     * </pre>
-     * @throws MobyException if the XML document is invalid
-     *************************************************************************/
-    public MobyService[] extractServices (String xml)
-	throws MobyException {
-
-	Document document = loadDocument (new ByteArrayInputStream (xml.getBytes()));
-	NodeList list = document.getElementsByTagName ("Service");
-	MobyService[] results = new MobyService [list.getLength()];
-	for (int i = 0; i < list.getLength(); i++) {
-	    Element elem = (Element)list.item (i);
-	    MobyService service = new MobyService (elem.getAttribute ("serviceName"));
-	    service.setAuthority (elem.getAttribute ("authURI"));
-	    service.setLSID (elem.getAttribute ("lsid"));
-	    NodeList children = elem.getChildNodes();
-	    for (int j = 0; j < children.getLength(); j++) {
-		String nodeName = children.item (j).getNodeName();
-		if (nodeName.equals ("Description")) {
-		    service.setDescription (getFirstValue (children.item (j)));
-		} else if (nodeName.equals ("Category")) {
-		    service.setCategory (getFirstValue (children.item (j)));
-		} else if (nodeName.equals ("URL")) {
-		    service.setURL (getFirstValue (children.item (j)));
-		} else if (nodeName.equals ("signatureURL")) {
-		    service.setSignatureURL (getFirstValue (children.item (j)));
-		} else if (nodeName.equals ("contactEmail")) {
-		    service.setEmailContact (getFirstValue (children.item (j)));
-		} else if (nodeName.equals ("serviceType")) {
-		    service.setType (getFirstValue (children.item (j)));
-		    MobyServiceType mst = new MobyServiceType(service.getType());
-		    NamedNodeMap map = (children.item (j).getAttributes());
-			if (map != null) {
-				Node node = map.getNamedItemNS(children.item(j).getNamespaceURI(),"lsid");
-				if (node != null)
-					mst.setLSID(node.getNodeValue());
-			}
-			service.setServiceType(mst);
-		} else if (nodeName.equals ("authoritative")) {
-		    String authoritative = getFirstValue (children.item (j));
-		    service.setAuthoritative (authoritative.equals ("1") ? true : false);
-		} else if (nodeName.equals ("Input")) {
-		    // <Input>
-		    //   <!-- one or more Simple and/or Complex Primary articles -->
-		    //   <Simple articleName="NameOfArticle">
-		    //      ...
-		    //   </Simple>
-		    //   <Collection articleName="NameOfArticle">
-		    //      <Simple>......</Simple>
-		    //      <Simple>......</Simple>
-		    //   </Collection>
-		    // </Input>
-		    NodeList inputs = children.item (j).getChildNodes();
-		    for (int k = 0; k < inputs.getLength(); k++) {
-			if (inputs.item (k).getNodeName().equals ("Simple")) {
-			    MobyPrimaryDataSimple data = new MobyPrimaryDataSimple ((Element)inputs.item (k));
-			    service.addInput (data);
-			} else if (inputs.item (k).getNodeName().equals ("Collection")) {
-			    MobyPrimaryDataSet data = new MobyPrimaryDataSet ((Element)inputs.item (k));
-			    service.addInput (data);
-			}
-		    }
-		} else if (nodeName.equals ("Output")) {
-		    // <Output>
-		    //   <!-- one or more Simple and/or Complex Primary articles --> 
-		    // </Output>
-		    NodeList inputs = children.item (j).getChildNodes();
-		    for (int k = 0; k < inputs.getLength(); k++) {
-			if (inputs.item (k).getNodeName().equals ("Simple")) {
-			    MobyPrimaryDataSimple data = new MobyPrimaryDataSimple ((Element)inputs.item (k));
-			    service.addOutput (data);
-			} else if (inputs.item (k).getNodeName().equals ("Collection")) {
-			    MobyPrimaryDataSet data = new MobyPrimaryDataSet ((Element)inputs.item (k));
-			    service.addOutput (data);
-			}
-		    }
-
-		} else if (nodeName.equals ("secondaryArticles")) {
-		    // <Parameter articleName="NameOfArticle">
-		    //   ...
-		    // </Parameter>
-		    NodeList parameters = children.item (j).getChildNodes();
-		    for (int k = 0; k < parameters.getLength(); k++) {
-			if (parameters.item (k).getNodeName().equals ("Parameter")) {
-			    MobySecondaryData data = new MobySecondaryData ((Element)parameters.item (k));
-			    service.addInput (data);
-			}
-		    }
-		}
-	    }
-	    results [i] = service;
-	}
-	return results;
-    }
-
-    // protect against null values
-    protected String getFirstValue (Node child) {
-	Node node = child.getFirstChild();
-	if (node == null) return "";
-	String value = node.getNodeValue();
-	if (value == null) return "";
-	return value;
-    }
-    
-    protected String getFirstValue (NodeList children) {
-	if (children.item(0) != null && children.item(0).hasChildNodes()) {
-	    children.item(0).normalize();
-	    return getFirstValue (children.item(0));
-	}
-	return "";
-    }
-
-    /**************************************************************************
-     * 
-     * Implementing SimpleCache interface.
-     *
-     * Why to have an interface for such trivial thing? Well, because
-     * I needed to overwrite the caching mechanism in the subclasses
-     * so I needed to have all caching functions as separate methods -
-     * that's why I have collect them in an interface.
-     *
-     *************************************************************************/
-    private Hashtable<String,Object> cache;   // this is the cache itself
-    private boolean useCache;  // this signal that we are actually caching things
-
-    // not used here
-    public String createId (String rootName,
-			    String semanticType, String syntaxType,
-			    long lastModified,
-			    Properties props) {
-	return ""; // not used here
-    }
-
-    // check existence of a cached object
-    public boolean existsInCache (String id) {
-	synchronized (cache) {
-	    if (useCache) return cache.containsKey (id);
-	    else return false;
-	}
-    }
-
-    // retrieve from cache
-    public Object getContents (String id) {
-	synchronized (cache) {
-	    if (useCache) return cache.get (id);
-	    else return null;
-	}
-    }
-
-    // cache an object
-    public void setContents (String id, java.lang.Object data) {
-	synchronized (cache) {
-	    if (useCache) cache.put (id, data);
-	}
-    }
-
-    // in this implementation, it clears the whole cache, regardless
-    // what 'id' is passed
-    public void removeFromCache (String id) {
-	cache.clear();
-    }
-
-    /**************************************************************************
-     *
-     * And the other methods related to caching (but not part of the
-     * SimpleCache interface).
-     *
-     **************************************************************************/
-
-    /**************************************************************************
-     * By default, caching is enabled to reduce network traffic.
-     * Setting this to false will clear the cache, and not cache any
-     * further calls unless it is set to true again. <p>
-     *
-     * @param shouldCache whether retrieveXXX call results should be
-     * cached in case they are called again (i.e. don't request
-     * MobyCentral every time)
-     **************************************************************************/
-    public void setCacheMode (boolean shouldCache) {
-	useCache = shouldCache;
-	if (! useCache)
-	    removeFromCache (null);
-    }
-
-    /**************************************************************************
-     * Find if caching is currently enabled.
-     *
-     * @return true if caching is enabled
-     **************************************************************************/
-    public boolean getCacheMode(){
-	return useCache;
-    }
-
-    /**************************************************************************
-     * Parses and imports the following XML.
-     * <pre>
-     * &lt;serviceNames&gt;
-     *   &lt;serviceName name="serviceName" authURI='authority.info.here'/&gt;
-     *   ...
-     *   ...
-     * &lt;/serviceNames&gt;
-     * </pre>
-     *
-     * @deprecated Replaced by {@link
-     * #getServiceNamesByAuthority}. The reason is that this method
-     * returns a random result if there are more services with the
-     * same name but belonging to different authorities. <p>
-     *
-     *************************************************************************/
-    public Map<String,String> getServiceNames()
-	throws MobyException {
-
-	String result = (String)doCall ("retrieveServiceNames",
-					new Object[] {});
-	// parse returned XML
-	Map<String,String> results = new TreeMap<String,String> (getStringComparator());
-	Document document = loadDocument (new ByteArrayInputStream (result.getBytes()));
-	NodeList list = document.getElementsByTagName ("serviceName");
-	for (int i = 0; i < list.getLength(); i++) {
-	    Element elem = (Element)list.item (i);
-	    results.put (elem.getAttribute ("name"),
-			 elem.getAttribute ("authURI"));
-	}
-
-	return results;
-    }
-
-    /**************************************************************************
-     * Parses and imports the following XML.
-     * <pre>
-     * &lt;serviceNames&gt;
-     *   &lt;serviceName name="serviceName" lsid="..." authURI='authority.info.here'/&gt;
-     *   ...
-     *   ...
-     * &lt;/serviceNames&gt;
-     * </pre>
-     *
-     * @return a Map which has authorities as keys, and String arrays
-     * with service names as a values.
-     *************************************************************************/
-    public Map getServiceNamesByAuthority()
-	throws MobyException {
-	String result = getServiceNamesByAuthorityAsXML();
-	return createServicesByAuthorityFromXML (result, true);
-    }
-
-    /**************************************************************************
-     * Similar to {@link #getServiceNamesByAuthority} but the
-     * resulting Map contains slightly more. <p>
-     *
-     * @return a Map which has authorities as keys, and arrays of
-     * MobyServices as a values. Each MobyService is filled with its
-     * name, authority and LSID.
-     *************************************************************************/
-    public Map getServicesByAuthority()
-	throws MobyException {
-	String result = getServiceNamesByAuthorityAsXML();
-	return createServicesByAuthorityFromXML (result, false);
-    }
-
-    //
-    protected String getServiceNamesByAuthorityAsXML()
-	throws MobyException {
-	return (String)doCall ("retrieveServiceNames",
-			       new Object[] {});
-    }
-
-    // if onlyNames == true
-    //    Map: authority name -> String[]
-    //                        (filled with service namea)
-    // else
-    //    Map: authority name -> MobyService[]
-    //                        (filled with service name, authority and lsid)
-    protected Map createServicesByAuthorityFromXML (String result,
-						    boolean onlyNames)
-	throws MobyException {
-
-	// parse returned XML
-	Map results = new TreeMap (getStringComparator());
-	Document document = loadDocument (new ByteArrayInputStream (result.getBytes()));
-	NodeList list = document.getElementsByTagName ("serviceName");
-	for (int i = 0; i < list.getLength(); i++) {
-	    Element elem = (Element)list.item (i);
-	    String name = elem.getAttribute ("name");
-	    String auth = elem.getAttribute ("authURI");
-	    Vector<Object> v =
-		(results.containsKey (auth) ? (Vector)results.get (auth) : new Vector<Object>());
-	    if (onlyNames) {
-		v.addElement (name);
-	    } else {
-		MobyService ms = new MobyService (name);
-		ms.setAuthority (auth);
-		ms.setLSID (elem.getAttribute ("lsid"));
-		v.addElement (ms);
-	    }
-	    results.put (auth, v);
-	}
-
-	// change values of type Vector to MobyService[] or String[]
-	for (Iterator it = results.entrySet().iterator(); it.hasNext(); ) {
-	    Map.Entry entry = (Map.Entry)it.next();
-	    Vector v = (Vector)entry.getValue();
-	    if (onlyNames) {
-		String[] sNames = new String [v.size()];
-		v.copyInto (sNames);
-		entry.setValue (sNames);
-	    } else {
-		MobyService[] mss = new MobyService [v.size()];
-		v.copyInto (mss);
-		entry.setValue (mss);
-	    }
-	}
-
-	return results;
-    }
-
-    /**************************************************************************
-     * Parses and imports the following XML.
-     * <pre>
-     *  &lt;serviceProviders&gt;
-     *     &lt;serviceProvider name="authority.URI.here"/&gt;
-     *          ...
-     *          ...
-     *  &lt;/serviceProviders&gt;
-     * </pre>
-     *************************************************************************/
-    public String[] getProviders()
-	throws MobyException {
-
-	String cacheId = "retrieveServiceProviders";
-	String[] cachedResults = (String[])getContents (cacheId);
-	if (cachedResults != null)
-	    return cachedResults;
-
-	String result = (String)doCall ("retrieveServiceProviders",
-					new Object[] {});
-
-	// parse returned XML
-	Document document = loadDocument (new ByteArrayInputStream (result.getBytes()));
-	NodeList list = document.getElementsByTagName ("serviceProvider");
-	String[] results = new String [list.getLength()];
-	for (int i = 0; i < list.getLength(); i++)
-	    results[i] = ((Element)list.item (i)).getAttribute ("name");
-
-	// Add this data to the cache in case we get called again
-	setContents (cacheId, results);
-
-	return results;
-    }
-
-    /**************************************************************************
-     * Parses and imports the following XML.
-     * <pre>
-     *  &lt;serviceTypes&gt;
-     *     &lt;serviceType name="serviceName" lsid="..."&gt;
-     *            &lt;Description&gt;&lt;![CDATA[free text description here]]&gt;&lt;/Description&gt;
-     *            &lt;contactEmail&gt;...&lt;/contactEmail&gt;
-     *            &lt;authURI&gt;...&lt;/authURI&gt;
-     *     &lt;/serviceType&gt;
-     *          ...
-     *          ...
-     *  &lt;/serviceTypes&gt;
-     * </pre>
-     *************************************************************************/
-    public Map getServiceTypes()
-	throws MobyException {
-	String result = getServiceTypesAsXML();
- 	Map results = new TreeMap (getStringComparator());
-	MobyServiceType[] types = createServiceTypesFromXML (result);
-	for (int i = 0; i < types.length; i++) {
-	    results.put (types[i].getName(),
-			 types[i].getDescription());
-	}
-	return results;
-    }
-
-    //
-    protected String getServiceTypesAsXML()
-	throws MobyException {
-	return (String)doCall ("retrieveServiceTypes",
-			       new Object[] {});
-    }
-
-    // but be aware that the created MobyServiceTypes are not complete
-    // - they do not have the relationship information; that's why
-    // this method is not public; the full service types are available
-    // from CentralDigest implementations
-    protected MobyServiceType[] createServiceTypesFromXML (String result)
-	throws MobyException {
-
-	// parse returned XML
-	Document document = loadDocument (new ByteArrayInputStream (result.getBytes()));
-	NodeList list = document.getElementsByTagName ("serviceType");
-	if (list == null || list.getLength() == 0)
-	    return new MobyServiceType[] {};
-	MobyServiceType[] results = new MobyServiceType [list.getLength()];
-	for (int i = 0; i < list.getLength(); i++) {
-	    Element elem = (Element)list.item (i);
-	    MobyServiceType st = new MobyServiceType (elem.getAttribute ("name"));
-	    st.setLSID (elem.getAttribute ("lsid"));
-	    st.setDescription (getFirstValue (elem.getElementsByTagName ("Description")));
-	    st.setEmailContact (getFirstValue (elem.getElementsByTagName ("contactEmail")));
-	    st.setAuthority (getFirstValue (elem.getElementsByTagName ("authURI")));
-	    results[i] = st;
-	}
- 	java.util.Arrays.sort (results);
-	return results;
-    }
-
-    /**************************************************************************
-     * Parses and imports the following XML.
-     * <pre>
-     *  &lt;Namespaces&gt;
-     *     &lt;Namespace name="namespace" lsid="..."&gt;
-     *            &lt;Description&gt;&lt;![CDATA[free text description here]]&gt;&lt;/Description&gt;
-     *            &lt;contactEmail&gt;...&lt;/contactEmail&gt;
-     *            &lt;authURI&gt;...&lt;/authURI&gt;
-     *     &lt;/Namespace&gt;
-     *          ...
-     *          ...
-     *  &lt;/Namespaces&gt;
-     * </pre>
-     *************************************************************************/
-    public MobyNamespace[] getFullNamespaces()
-	throws MobyException {
-
-	String result = getNamespacesAsXML();
-	return createNamespacesFromXML (result);
-    }
-
-    //
-    protected String getNamespacesAsXML()
-	throws MobyException {
-	return (String)doCall ("retrieveNamespaces",
-			       new Object[] {});
-    }
-
-    //
-    protected MobyNamespace[] createNamespacesFromXML (String result)
-	throws MobyException {
-
-	// parse returned XML
-	Document document = loadDocument (new ByteArrayInputStream (result.getBytes()));
-	NodeList list = document.getDocumentElement().getElementsByTagName ("Namespace");
-	if (list == null || list.getLength() == 0) {
-	    return new MobyNamespace[] {};
-	}
-	MobyNamespace[] results = new MobyNamespace [list.getLength()];
-	for (int i = 0; i < list.getLength(); i++) {
-	    Element elem = (Element)list.item (i);
-	    MobyNamespace nm = new MobyNamespace (elem.getAttribute ("name"));
-	    nm.setLSID (elem.getAttribute ("lsid"));
-	    nm.setDescription (getFirstValue (elem.getElementsByTagName ("Description")));
-	    nm.setEmailContact (getFirstValue (elem.getElementsByTagName ("contactEmail")));
-	    nm.setAuthority (getFirstValue (elem.getElementsByTagName ("authURI")));
-	    results[i] = nm;
-	}
-
- 	java.util.Arrays.sort (results);
-	return results;
-    }
-
-    /**************************************************************************
-     *
-     * @deprecated Replaced by {@link #getFullNamespaces} that gives
-     * more information for the same price. <p>
-     *************************************************************************/
-    public Map getNamespaces()
-	throws MobyException {
-
- 	Map results = new TreeMap (getStringComparator());
-	MobyNamespace[] namespaces = getFullNamespaces();
-	for (int i = 0; i < namespaces.length; i++) {
-	    results.put (namespaces[i].getName(),
-			 namespaces[i].getDescription());
-	}
-	return results;
-    }
-
-    /**************************************************************************
-     * Parses and imports the following XML.
-     * <pre>
-     *  &lt;objectNames&gt;
-     *     &lt;Object name="objectName" lsid="..."&gt;
-     *            &lt;Description&gt;&lt;![CDATA[free text description here]]&gt;&lt;/Description&gt;
-     *     &lt;/Object&gt;
-     *          ...
-     *          ...
-     *  &lt;/objectNames&gt;
-     * </pre>
-     *************************************************************************/
-    public Map getDataTypeNames()
-	throws MobyException {
-	String result = getDataTypeNamesAsXML();
-	return createDataTypeNamesFromXML (result, true);
-    }
-
-    //
-    protected String getDataTypeNamesAsXML()
-	throws MobyException {
-	return (String)doCall ("retrieveObjectNames",
-			       new Object[] {});
-    }
-
-    // if onlyNames == true
-    //    Map: data type name -> description (String)
-    // else
-    //    Map: data type name -> MobyDataType[]
-    //                        (filled with name, description, and lsid)
-    protected Map createDataTypeNamesFromXML (String result,
-					      boolean onlyNames)
-	throws MobyException {
-
-	// parse returned XML
-	Map results = new TreeMap (getStringComparator());
-	Document document = loadDocument (new ByteArrayInputStream (result.getBytes()));
-	NodeList list = document.getElementsByTagName ("Object");
-	for (int i = 0; i < list.getLength(); i++) {
-	    Element elem = (Element)list.item (i);
-	    String name = elem.getAttribute ("name");
-	    if (name == null)
-		continue;  // ignore no-named data types
-	    String desc = "";
-	    NodeList children = elem.getChildNodes();
-	    for (int j = 0; j < children.getLength(); j++) {
-		if (children.item (j).getNodeName().equals ("Description")) {
-		    desc = getFirstValue (children.item (j));
-		    break;
-		}
-	    }
-	    if (onlyNames) {
-		results.put (name, desc);
-	    } else {
-		MobyDataType dt = new MobyDataType (name);
-		dt.setDescription (desc);
-		dt.setLSID (elem.getAttribute ("lsid"));
-		results.put (name, dt);
-	    }
-	}
-	return results;
-    }
-
-
-    /**************************************************************************
-     * Parses and imports the following XML. An example:
-     *
-     * <pre>
-     * &lt;retrieveObjectDefinition&gt;
-     *   &lt;objectType lsid="..."&gt;go_term&lt;/objectType&gt;
-     *   &lt;Description&gt;&lt;![CDATA[A very lightweight object holding a GO term name and its definition]]&gt;&lt;/Description&gt;
-     *   &lt;authURI&gt;http://www.illuminae.com&lt;/authURI&gt;
-     *   &lt;contactEmail&gt;markw at illuminae.com&lt;/contactEmail&gt;
-     *   &lt;Relationship relationshipType='urn:lsid:biomoby.org:objectrelation:isa'&gt;
-     *      &lt;objectType articleName=''&gt;urn:lsid:biomoby.org:objectclass:object&lt;/objectType&gt;
-     *   &lt;/Relationship&gt;
-     *   &lt;Relationship relationshipType='urn:lsid:biomoby.org:objectrelation:hasa'&gt;
-     *      &lt;objectType articleName='Term'&gt;urn:lsid:biomoby.org:objectclass:string&lt;/objectType&gt;
-     *      &lt;objectType articleName='Definition'&gt;urn:lsid:biomoby.org:objectclass:string&lt;/objectType&gt;
-     *   &lt;/Relationship&gt;
-     *   &lt;Relationship relationshipType='urn:lsid:biomoby.org:objectrelation:has'&gt;
-     *      &lt;objectType articleName='Problems'&gt;urn:lsid:biomoby.org:objectclass:string&lt;/objectType&gt;
-     *      &lt;objectType articleName='Issues'&gt;urn:lsid:biomoby.org:objectclass:string&lt;/objectType&gt;
-     *   &lt;/Relationship&gt;
-     * &lt;/retrieveObjectDefinition&gt;
-     * </pre>
-     *************************************************************************/
-    public MobyDataType getDataType (String dataTypeName)
-	throws MobyException, NoSuccessException {
-
-	String result = getDataTypeAsXML (dataTypeName);
-	return createDataTypeFromXML (result, dataTypeName);
-    }
-
-    public MobyDataType[] getDataTypes()
-	throws MobyException, NoSuccessException {
-	Map<String,String> datatypeMap = getDataTypeNames();
-	MobyDataType[] datatypes = new MobyDataType[datatypeMap.size()];
-	int i = 0;
-	for(String dataTypeName: datatypeMap.keySet()){
-	    datatypes[i++] = getDataType(dataTypeName);
-	}
-	return datatypes;
-    }
-
-    protected String getDataTypeAsXML (String dataTypeName)
-	throws MobyException, NoSuccessException {
-
-	return (String)doCall ("retrieveObjectDefinition",
-			       new Object[] {
-				   "<retrieveObjectDefinition>" +
-				   "<objectType>" + dataTypeName + "</objectType>" +
-				   "</retrieveObjectDefinition>"
-			       });
-    }
-
-    protected MobyDataType createDataTypeFromXML (String xmlSource, String dataTypeName)
-	throws MobyException, NoSuccessException {
-
-	// parse returned XML
-	Document document = loadDocument (new ByteArrayInputStream (xmlSource.getBytes()));
-	NodeList list = document.getElementsByTagName ("retrieveObjectDefinition");
-	if (list == null || list.getLength() == 0)
-	    throw new NoSuccessException ("Data Type name was not found.",
-					  dataTypeName);
-	MobyDataType data = null;
-	Element elem = (Element)list.item (0);
-	NodeList children = elem.getChildNodes();
-
-	// first find the "real" (LSID-ized) data type name
-	for (int j = 0; j < children.getLength(); j++) {
-	    String nodeName = children.item (j).getNodeName();
-	    if (nodeName.equals ("objectType")) {
-		data = new MobyDataType (getFirstValue (children.item (j)));
-		data.setLSID ( ((Element)children.item (j) ).getAttribute ("lsid"));
-		break;
-	    }
-	}
-
-	// if not found (unprobable) use the name given by the caller
-	if (data == null)
-	    data = new MobyDataType (dataTypeName);
-
-	// now fill the data type object with the rest of attributes
-	for (int j = 0; j < children.getLength(); j++) {
-	    String nodeName = children.item (j).getNodeName();
-	    if (nodeName.equals ("Description")) {
-		data.setDescription (getFirstValue (children.item (j)));
-	    } else if (nodeName.equals ("authURI")) {
-		data.setAuthority (getFirstValue (children.item (j)));
-	    } else if (nodeName.equals ("contactEmail")) {
-		data.setEmailContact (getFirstValue (children.item (j)));
-	    } else if (nodeName.equals ("Relationship")) {
-		String relationshipType = ((Element)children.item (j)).getAttribute ("relationshipType");
-		if (relationshipType.endsWith ("isa")) {
-
-		    NodeList parents = children.item (j).getChildNodes();
-		    for (int k = 0; k < parents.getLength(); k++) {
-			if (parents.item (k).getNodeName().equals ("objectType")) {
-			    data.addParentName (getFirstValue (parents.item (k)));
-			}
-		    }
-		} else if (relationshipType.endsWith ("hasa")) {
-
-		    NodeList belows = children.item (j).getChildNodes();
-		    for (int k = 0; k < belows.getLength(); k++) {
-			if (belows.item (k).getNodeName().equals ("objectType")) {
-			    data.addChild ( ((Element)belows.item (k)).getAttribute ("articleName"),
-					    getFirstValue (belows.item (k)),
-					    Central.iHASA );
-			}
-		    }
-		} else if (relationshipType.endsWith ("has")) {
-
-		    NodeList belows = children.item (j).getChildNodes();
-		    for (int k = 0; k < belows.getLength(); k++) {
-			if (belows.item (k).getNodeName().equals ("objectType")) {
-			    data.addChild ( ((Element)belows.item (k)).getAttribute ("articleName"),
-					    belows.item (k).getFirstChild().getNodeValue(),
-					    Central.iHAS );
-			}
-		    }
-		}
-	    }
-	}
-	return data;
-    }
-
-    /**************************************************************************
-     *
-     *************************************************************************/
-    public String getServiceWSDL (String serviceName)
-	throws MobyException, NoSuccessException {
-
-	Map names = getServiceNames();
-
-	for (Iterator it = names.entrySet().iterator(); it.hasNext(); ) {
-	    Map.Entry entry = (Map.Entry)it.next();
-	    if ( ((String)entry.getKey()).equals (serviceName) )
-		return getServiceWSDL (serviceName, (String)entry.getValue());
-	}
-
-	throw new NoSuccessException ("Service not found.", serviceName);
-    }
-
-    /**************************************************************************
-     *
-     *************************************************************************/
-    public String getServiceWSDL (String serviceName, String authority)
-	throws MobyException, NoSuccessException {
-
-	String cacheId = "getServiceWSDL" + serviceName + ":" + authority;
-	String cachedResults = (String)getContents (cacheId);
-	if (cachedResults != null)
-	    return cachedResults;
-	
-	String result =
-	    (String)doCall ("retrieveService",
-			    new Object[] {
-				"<retrieveService>" +
-				  "<Service authURI=\"" + authority + "\" serviceName=\"" + serviceName + "\"/>" +
-				"</retrieveService>"
-			    });
-
-	// parse returned XML
-	Document document = loadDocument (new ByteArrayInputStream (result.getBytes()));
-	Element service = document.getDocumentElement();
-	Node wsdl = service.getFirstChild();
-	if (wsdl == null)
-	    throw new NoSuccessException ("Service not found OR WSDL is not available.",
-					   serviceName + " (" + authority + ")");
-
-	String results = wsdl.getNodeValue();
-	setContents (cacheId, results);
-	return results;
-    }
-
-    /*************************************************************************
-     *
-     *************************************************************************/
-    public String getRegisterDataTypeXML (MobyDataType dataType) {
-
-	// build the ISA tag (expressing hierarchy of data types)
-	String[] names = dataType.getParentNames();
-	StringBuffer buf = new StringBuffer();
-	for (int i = 0; i < names.length; i++) {
-	    buf.append ("<objectType>");
-	    buf.append (names[i]);
-	    buf.append ("</objectType>");
-	    buf.append ("\n");
-	}
-
-	// build the HASA/HAS tags (expressing containments of data types)
-	MobyRelationship[] children = dataType.getChildren();
-	StringBuffer buf2 = new StringBuffer();  // for HASA
-	StringBuffer buf3 = new StringBuffer();  // for HAS
-	for (int i = 0; i < children.length; i++) {
-	    if (children[i].getRelationshipType() == Central.iHASA) {
-		buf2.append ("<objectType articleName=\"");
-		buf2.append (children[i].getName());
-		buf2.append ("\">");
-		buf2.append (children[i].getDataTypeName());
-		buf2.append ("</objectType>");
-	    } else if (children[i].getRelationshipType() == Central.iHAS) {
-		buf3.append ("<objectType articleName=\"");
-		buf3.append (children[i].getName());
-		buf3.append ("\">");
-		buf3.append (children[i].getDataTypeName());
-		buf3.append ("</objectType>");
-	    }
-	}
-
-	return
-	    "<registerObjectClass>" +
-	    "<objectType>" + dataType.getName() + "</objectType>" +
-	    "<Description><![CDATA[" + dataType.getDescription() + "]]>" +
-	    "</Description>" +
-	    "<Relationship relationshipType=\"ISA\">" + new String (buf) +
-	    "</Relationship>" +
-	    "<Relationship relationshipType=\"HASA\">" + new String (buf2) +
-	    "</Relationship>" +
-	    "<Relationship relationshipType=\"HAS\">" + new String (buf3) +
-	    "</Relationship>" +
-	    "<authURI>" + dataType.getAuthority() + "</authURI>" +
-	    "<contactEmail>" + dataType.getEmailContact() + "</contactEmail>" +
-	    "</registerObjectClass>";
-    }
-
-    /*************************************************************************
-     *
-     *************************************************************************/
-    public void registerDataType (MobyDataType dataType)
-	throws MobyException, NoSuccessException, PendingCurationException {
-
-	String result =
-	    (String)doCall ("registerObjectClass",
-			    new Object[] { getRegisterDataTypeXML (dataType) });
-	dataType.setId (checkRegistration (result, dataType)[0]);
-    }
-
-    /*************************************************************************
-     * B
-     *************************************************************************/
-    public void unregisterDataType (MobyDataType dataType)
-	throws MobyException, NoSuccessException, PendingCurationException {
-	String result =
-	    (String)doCall ("deregisterObjectClass",
-			    new Object[] {
-				"<deregisterObjectClass>" +
-				  "<objectType>" + dataType.getName() + "</objectType>" +
-				"</deregisterObjectClass>"
-			    });
-	checkRegistration (result, dataType);
-    }
-
-    /*************************************************************************
-     *
-     *************************************************************************/
-    public String getRegisterServiceTypeXML (MobyServiceType serviceType) {
-
-	// build the ISA tag (expressing hierarchy of service types)
-	String[] names = serviceType.getParentNames();
-	StringBuffer buf = new StringBuffer();
-	for (int i = 0; i < names.length; i++) {
-	    buf.append ("<serviceType>");
-	    buf.append (names[i]);
-	    buf.append ("</serviceType>");
-	    buf.append ("\n");
-	}
-
-	return
-	    "<registerServiceType>" +
-	    "<serviceType>" + serviceType.getName() + "</serviceType>" +
-	    "<contactEmail>" + serviceType.getEmailContact() + "</contactEmail>" +
-	    "<authURI>" + serviceType.getAuthority() + "</authURI>" +
-	    "<Description><![CDATA[" + serviceType.getDescription() + "]]>" +
-	    "</Description>" +
-	    "<Relationship relationshipType=\"ISA\">" + new String (buf) +
-	    "</Relationship>" +
-	    "</registerServiceType>";
-    }
-
-    /*************************************************************************
-     *
-     *************************************************************************/
-    public void registerServiceType (MobyServiceType serviceType)
-	throws MobyException, NoSuccessException, PendingCurationException {
-
-	String result =
-	    (String)doCall ("registerServiceType",
-			    new Object[] { getRegisterServiceTypeXML (serviceType) });
-	serviceType.setId (checkRegistration (result, serviceType)[0]);
-    }
-
-    /*************************************************************************
-     *
-     *************************************************************************/
-    public void unregisterServiceType (MobyServiceType serviceType)
-	throws MobyException, NoSuccessException, PendingCurationException {
-	String result =
-	    (String)doCall ("deregisterServiceType",
-			    new Object[] {
-				"<deregisterServiceType>" +
-				  "<serviceType>" + serviceType.getName() + "</serviceType>" +
-				"</deregisterServiceType>"
-			    });
-	checkRegistration (result, serviceType);
-    }
-
-    /*************************************************************************
-     *
-     *************************************************************************/
-    public String getRegisterNamespaceXML (MobyNamespace namespace) {
-	return
-	    "<registerNamespace>" +
-	    "<namespaceType>" + namespace.getName() + "</namespaceType>" +
-	    "<contactEmail>" + namespace.getEmailContact() + "</contactEmail>" +
-	    "<authURI>" + namespace.getAuthority() + "</authURI>" +
-	    "<Description><![CDATA[" + namespace.getDescription() + "]]>" +
-	    "</Description>" +
-	    "</registerNamespace>";
-    }
-
-    /*************************************************************************
-     *
-     *************************************************************************/
-    public void registerNamespace (MobyNamespace namespace)
-	throws MobyException, NoSuccessException, PendingCurationException {
-	String result =
-	    (String)doCall ("registerNamespace",
-			    new Object[] { getRegisterNamespaceXML (namespace) });
-	namespace.setId (checkRegistration (result, namespace)[0]);
-    }
-
-    /*************************************************************************
-     *
-     *************************************************************************/
-    public void unregisterNamespace (MobyNamespace namespace)
-	throws MobyException, NoSuccessException, PendingCurationException {
-	String result =
-	    (String)doCall ("deregisterNamespace",
-			    new Object[] {
-				"<deregisterNamespace>" +
-				  "<namespaceType>" + namespace.getName() + "</namespaceType>" +
-				"</deregisterNamespace>"
-			    });
-	checkRegistration (result, namespace);
-    }
-
-    /*************************************************************************
-     *
-     *************************************************************************/
-    public String getRegisterServiceXML (MobyService service) {
-	return
-	    "<registerService>" +
-	    "<Category>" + service.getCategory() + "</Category>" +
-	    "<serviceName>" + service.getName() + "</serviceName>" +
-	    "<serviceType>" + service.getType() + "</serviceType>" +
-	    "<serviceLSID>" + (service.getLSID() == null ? "" : service.getLSID().trim() )+ "</serviceLSID>" +
-	    "<authURI>" + service.getAuthority() + "</authURI>" +
-	    "<signatureURL>" + escapeXML (service.getSignatureURL()) + "</signatureURL>" +
-	    "<URL>" + escapeXML (service.getURL()) + "</URL>" +
-	    "<contactEmail>" + service.getEmailContact() + "</contactEmail>" +
-	    "<authoritativeService>" + (service.isAuthoritative() ? "1" : "0") + "</authoritativeService>" +
-	    "<Description><![CDATA[" + service.getDescription() + "]]>" +
-	    "</Description>" +
-	    buildPrimaryInputTag (service) + 
-	    buildSecondaryInputTag (service) + 
-	    buildOutputTag (service) + 
-	    "</registerService>";
-    }
-
-    /*************************************************************************
-     *
-     *************************************************************************/
-    public void registerService (MobyService service)
-	throws MobyException, NoSuccessException, PendingCurationException {
-
-	String result =
-	    (String)doCall ("registerService",
-			    new Object[] { getRegisterServiceXML (service) });
-	String[] registered = checkRegistration (result, service);
-	service.setId (registered [0]);
-	service.setRDF (registered [1]);
-	String pathToRDF = service.getPathToRDF();
-	if ( ! pathToRDF.equals ("") ) {
-	    File fileRDF = new File (pathToRDF);
-	    try {
-		PrintStream fileout = new PrintStream (new FileOutputStream (fileRDF));
-		fileout.println (registered [1]);
-		fileout.close();
-	    } catch (IOException e) {
-		StringBuffer buf = new StringBuffer (100);
-		buf.append ("Failed to save RDF in '");
-		buf.append (fileRDF.getAbsolutePath() + "'. ");
-		buf.append (e.toString());
-		try {
-		    File tmpFile = File.createTempFile (service.getName() + "-", ".rdf");
-		    PrintStream fileout = new PrintStream (new FileOutputStream (tmpFile));
-		    fileout.println (registered [1]);
-		    fileout.close();
-		    buf.append ("\nReturned RDF file was therefore stored in: ");
-		    buf.append (tmpFile.getAbsolutePath());
-		} catch (IOException e2) {
-		    buf.append ("\nEven saving in a temporary file failed: ");
-		    buf.append (e2.toString());
-		}
-		throw new MobyException (buf.toString());
-	    }
-	}
-    }
-
-    /*************************************************************************
-     *
-     *************************************************************************/
-    public void unregisterService (MobyService service)
-	throws MobyException, NoSuccessException, PendingCurationException {
-	String result =
-	    (String)doCall ("deregisterService",
-			    new Object[] {
-				"<deregisterService>" +
-				  "<authURI>" + service.getAuthority() + "</authURI>" +
-				  "<serviceName>" + service.getName() + "</serviceName>" +
-				"</deregisterService>"
-			    });
-	checkRegistration (result, service);
-    }
-
-    /**************************************************************************
-     *
-     *************************************************************************/
-    public MobyService[] findService (String serviceType)
-	throws MobyException {
-	if (serviceType == null)
-	    return new MobyService[] {};
-	MobyService pattern = new MobyService ("dummy");
-	pattern.setCategory ("");
-	pattern.setType (serviceType);
-	return findService (pattern, null);
-    }
-
-    /**************************************************************************
-     *
-     *************************************************************************/
-    public MobyService[] findService (String[] keywords)
-	throws MobyException {
-	if (keywords == null)
-	    return new MobyService[] {};
-	return findService (null, keywords);
-    }
-
-    /**************************************************************************
-     *
-     *************************************************************************/
-    public MobyService[] findService (MobyService pattern)
-	throws MobyException {
-	if (pattern == null)
-	    return new MobyService[] {};
-	return findService (pattern, null);
-    }
-
-    /**************************************************************************
-     *
-     *************************************************************************/
-    public MobyService[] findService (MobyService pattern, String[] keywords)
-	throws MobyException {
-	return findService (pattern, keywords, true, true);
-    }
-
-    /**************************************************************************
-     * All 'findService' methods end up here.
-     *************************************************************************/
-    public MobyService[] findService (MobyService pattern, String[] keywords,
-				      boolean includeChildrenServiceTypes,
-				      boolean includeParentDataTypes)
-	throws MobyException {
-	if (pattern == null) {
-	    pattern = new MobyService ("dummy");
-	    pattern.setCategory ("");
-	}
-
-	String result =
-	    getServicesAsXML (pattern, keywords, includeChildrenServiceTypes, includeParentDataTypes);
-	MobyService[] services = extractServices (result);
-	return services;
-    }
-
-    // ...actually all 'findService' methods end up here
-    protected String getServicesAsXML (MobyService pattern, String[] keywords,
-				       boolean includeChildrenServiceTypes,
-				       boolean includeParentDataTypes)
-	throws MobyException {
-	String[] query = new String[] { 
-				"<findService>" +
-				buildQueryObject (pattern, keywords,
-						  includeParentDataTypes,
-						  includeChildrenServiceTypes,
-						  false) + 
-				"</findService>"
-	};
-	return (String)doCall ("findService", query);
-    }
-
-    /**************************************************************************
-     *
-     *************************************************************************/
-    public String call (String methodName, String inputXML)
-	throws MobyException {
-	Object result;
-	if (inputXML == null || inputXML.equals (""))
-	    result = doCall (methodName, new Object[] { });
-	else 
-	    result = doCall (methodName, new Object[] { inputXML });
-	return (String)result;
-    }
-
-    /**************************************************************************
-     *
-     *************************************************************************/
-    protected static String resultToString (Object result)
-	throws MobyException {
- 	if (result == null)
- 	    throw new MobyException ("Returned result is null.");
-	if (result instanceof String)
-	    return (String)result;
-	if (result instanceof String[]) {
-	    String[] tmp = (String[])result;
-	    StringBuffer buf = new StringBuffer();
-	    for (int i = 0; i < tmp.length; i++)
-		buf.append (tmp[i]);
-	    return new String (buf);
-	}
-	if (result instanceof byte[])
-	    return new String ((byte[])result);
-
-	throw new MobyException ("Unknown type of result: " + result.getClass().getName());
-    }
-
-    /**************************************************************************
-     *
-     *************************************************************************/
-    public boolean setDebug (boolean enabled) {
-	boolean oldMode = debug;
-	debug = enabled;
-	return oldMode;
-    }
-
-    /**************************************************************************
-     * Parses and imports the following XML.
-     * <pre>
-     * &lt;Relationships&gt;
-     *   &lt;Relationship relationshipType='urn:lsid:biomoby.org:servicerelation:isa'&gt;
-     *     &lt;serviceType&gt;urn:lsid:biomoby.org:servicetype:analysis&lt;/serviceType&gt;
-     *     &lt;serviceType&gt;urn:lsid:biomoby.org:servicetype:service&lt;/serviceType&gt;
-     *   &lt;/Relationship&gt;
-     * &lt;/Relationships&gt;
-     * </pre>
-     *************************************************************************/
-    public String[] getServiceTypeRelationships (String serviceTypeName,
-						 boolean expand)
-	throws MobyException {
-	String result = getServiceTypeRelationshipsAsXML (serviceTypeName, expand);
-	return createServiceTypeRelationshipsFromXML (result);
-    }
-
-    //
-    protected String getServiceTypeRelationshipsAsXML (String serviceTypeName,
-						       boolean expand)
-	throws MobyException {
-	return
-	    (String)doCall ("Relationships",
-			    new Object[] {
-				"<Relationship>" +
-				"<serviceType>" + serviceTypeName + "</serviceType>" +
-				"<relationshipType>" + Central.ISA + "</relationshipType>" +
-				"<expandRelationship>" + (expand ? "1" : "0") + "</expandRelationship>" +
-				"</Relationship>"
-			    });
-    }
-
-    //
-    protected String[] createServiceTypeRelationshipsFromXML (String result)
-	throws MobyException {
-
-	// parse returned XML
-	Vector<String> v = new Vector<String>();
-	Document document = loadDocument (new ByteArrayInputStream (result.getBytes()));
-	NodeList list = document.getElementsByTagName ("Relationship");
-	for (int i = 0; i < list.getLength(); i++) {
-	    Element elem = (Element)list.item (i);
-	    NodeList children = elem.getChildNodes();
-	    for (int j = 0; j < children.getLength(); j++) {
-		if (children.item (j).getNodeName().equals ("serviceType")) {
-		    v.addElement (getFirstValue (children.item (j)));
-		}
-	    }
-	}
-	String[] results = new String [v.size()];
-	v.copyInto (results);
-	return results;
-    }
-
-    /**************************************************************************
-     * Parses and imports the following XML.
-     * <pre>
-     *&lt;Relationships&gt;
-     *  &lt;Relationship relationshipType='urn:lsid:biomoby.org:objectrelation:isa'&gt;
-     *    &lt;objectType&gt;urn:lsid:biomoby.org:objectclass:virtualsequence&lt;/objectType&gt;
-     *    &lt;objectType&gt;urn:lsid:biomoby.org:objectclass:object&lt;/objectType&gt;
-     *  &lt;/Relationship&gt;
-     *  &lt;Relationship relationshipType='urn:lsid:biomoby.org:objectrelation:hasa'&gt;
-     *    &lt;objectType&gt;urn:lsid:biomoby.org:objectclass:string&lt;/objectType&gt;
-     *    &lt;objectType&gt;urn:lsid:biomoby.org:objectclass:integer&lt;/objectType&gt;
-     *  &lt;/Relationship&gt;
-     *&lt;/Relationships&gt;
-     * </pre>
-     *
-     * Added at Sun Feb 19 19:32:31 PHT 2006: it recognizes also an
-     * attributes 'lsid' and 'articleName' in &lt;objectType&gt; element.
-     *************************************************************************/
-    public Map getDataTypeRelationships (String dataTypeName)
-	throws MobyException {
-
-	String cacheId = "getDataTypeRelationships_" + dataTypeName;
-	Map cachedResults = (Map)getContents (cacheId);
-	if (cachedResults != null)
-	    return cachedResults;
-	
-	String result =
-	    (String)doCall ("Relationships",
-			    new Object[] {
-				"<Relationships>" +
-				"<objectType>" + dataTypeName + "</objectType>" +
-				"<relationshipType>" + Central.ISA + "</relationshipType>" +
-				"<relationshipType>" + Central.HASA + "</relationshipType>" +
-				"<relationshipType>" + Central.HAS + "</relationshipType>" +
-				"<expandRelationship>1</expandRelationship>" +
-				"</Relationships>"
-			    });
-
-	// parse returned XML
-	Map results = new HashMap();
-	Document document = loadDocument (new ByteArrayInputStream (result.getBytes()));
-	NodeList list = document.getElementsByTagName ("Relationship");
-
-	for (int i = 0; i < list.getLength(); i++) {
-	    Element elem = (Element)list.item (i);
-	    String relType = elem.getAttribute ("relationshipType");
-	    NodeList children = elem.getChildNodes();
-	    Vector<String> v = new Vector<String>();
-	    for (int j = 0; j < children.getLength(); j++) {
-		if (children.item (j).getNodeName().equals ("objectType")) {
-		    v.addElement (getFirstValue (children.item (j)));
-		}
-	    }
-	    String[] names = new String [v.size()];
-	    v.copyInto (names);
-	    results.put (relType, names);
-	}
-
-	setContents (cacheId, results);
-	return results;
-    }
-
-    /**************************************************************************
-     * Parses and imports the following XML.
-     * <pre>
-     *&lt;Relationships&gt;
-     *  &lt;Relationship relationshipType='urn:lsid:biomoby.org:objectrelation:isa'&gt;
-     *    &lt;objectType&gt;urn:lsid:biomoby.org:objectclass:virtualsequence&lt;/objectType&gt;
-     *    &lt;objectType&gt;urn:lsid:biomoby.org:objectclass:object&lt;/objectType&gt;
-     *  &lt;/Relationship&gt;
-     *&lt;/Relationships&gt;
-     * </pre>
-     *************************************************************************/
-    public String[] getDataTypeRelationships (String dataTypeName,
-					      String relationshipType)
-	throws MobyException {
-
-	String cacheId = "getDataTypeRelationships_" + dataTypeName + ":" + relationshipType;
-	String[] cachedResults = (String[])getContents (cacheId);
-	if (cachedResults != null)
-	    return cachedResults;
-
-	String result =
-	    (String)doCall ("Relationships",
-			    new Object[] {
-				"<Relationships>" +
-				"<objectType>" + dataTypeName + "</objectType>" +
-				"<relationshipType>" + relationshipType + "</relationshipType>" +
-				"<expandRelationship>1</expandRelationship>" +
-				"</Relationships>"
-			    });
-
-	// parse returned XML
-	Vector<String> v = new Vector<String>();
-	Document document = loadDocument (new ByteArrayInputStream (result.getBytes()));
-	NodeList list = document.getElementsByTagName ("Relationship");
-
-	// it should always be just one element in this list
-	for (int i = 0; i < list.getLength(); i++) {
-	    Element elem = (Element)list.item (i);
-	    NodeList children = elem.getChildNodes();
-	    for (int j = 0; j < children.getLength(); j++) {
-		if (children.item (j).getNodeName().equals ("objectType")) {
-		    v.addElement (getFirstValue (children.item (j)));
-		}
-	    }
-	}
-	String[] results = new String [v.size()];
-	v.copyInto (results);
-
-	setContents (cacheId, results);
-	return results;
-    }
-
-//     /**************************************************************************
-//      *
-//      *************************************************************************/
-//     public MobyRelationship[] getRelationships (String dataTypeName)
-// 	throws MobyException {
-// 	return null;
-//     }
-
-
-    /**************************************************************************
-     *
-     *************************************************************************/
-    public String getRegistryEndpoint() {
-	return endpoint.toString();
-    }
-
-    /**************************************************************************
-     *
-     *************************************************************************/
-    public String getRegistryNamespace() {
-	return uri;
-    }
-
-    /**************************************************************************
-     * Parses and imports the following XML.
-     * <pre>
-     * &lt;resourceURLs&gt;
-     *   &lt;Resource name="Service"         url="..." /&gt;
-     *   &lt;Resource name="Object"          url="..." /&gt;
-     *   &lt;Resource name="Namespace"       url="..." /&gt;
-     *   &lt;Resource name="ServiceInstance" url="..." /&gt;
-     *   &lt;Resource name="Full"            url="..." /&gt;
-     * &lt;/resourceURLs&gt;
-     * </pre>
-     *************************************************************************/
-    public MobyResourceRef[] getResourceRefs()
-	throws MobyException {
-
-	String cacheId = "retrieveResourceURLs";
-	MobyResourceRef[] cachedResults = (MobyResourceRef[])getContents (cacheId);
-	if (cachedResults != null)
-	    return cachedResults;
-
-	String result = (String)doCall ("retrieveResourceURLs",
-					new Object[] {});
-
-	// parse returned XML
-	Vector<MobyResourceRef> v = new Vector<MobyResourceRef>();
-	Document document = loadDocument (new ByteArrayInputStream (result.getBytes()));
-	NodeList list = document.getElementsByTagName ("Resource");
-	for (int i = 0; i < list.getLength(); i++) {
-	    Element elem = (Element)list.item (i);
-	    try {
-		v.addElement
-		    (new MobyResourceRef (elem.getAttribute ("name"),
-					  new URL ((String)elem.getAttribute ("url")),
-					  elem.getAttribute ("type")));
-	    } catch (MalformedURLException e2) {
-		if (debug)
-		    System.err.println ("Bad URL: " + elem.getAttribute ("url"));
-	    }
-	}
-
-	MobyResourceRef[] results = new MobyResourceRef [v.size()];
-	v.copyInto (results);
-
-	// Add this data to the cache in case we get called again
-	setContents (cacheId, results);
-
-	return results;
-    }
-
-    /**************************************************************************
-     *
-     *************************************************************************/
-    public InputStream getResource (String resourceName)
-	throws MobyException {
-
-	MobyResourceRef[] resourceRefs = getResourceRefs();
-	for (int i = 0; i < resourceRefs.length; i++) {
-	    if (resourceName.equalsIgnoreCase (resourceRefs[i].getResourceName())) {
-		return Utils.getInputStream (resourceRefs[i].getResourceLocation());
-	    }
-	}
-	throw new MobyException ("No resource found for '" + resourceName + "'.");
-    }
-
-   /**************************************************************************
-     * Return a case-insensitive comparator of Strings. It is used to
-     * create various TreeMaps where keys are strings.
-     *************************************************************************/
-    protected static Comparator getStringComparator() {
-	return new Comparator() {
-		public int compare (Object o1, Object o2) {
-		    return ((String)o1).compareToIgnoreCase ((String)o2);
-		}
-	    };
-    }
-    
-    // cache URL/URI so we only check once 
-    private static String CHECKED_URL = null;
-    private static String CHECKED_URI = null;
-    
-    /**
-     * Using this method to get a Central object will ensure that other parts of the org.biomoby.shared 
-     * class hierarchy that implicitly check the registry will use the same cache.  Otherwise, methods
-     * such as MobyNamespace.getNamespace() must be passed a Central object parameter as well.
-     *
-     * @return a CentralImpl using the default Central URI, and currently a class implementing a caching mechanism
-     */
-    public static CentralImpl getDefaultCentral() throws MobyException{
-	return getDefaultCentral(null);
-    }
-
-    public static CentralImpl getDefaultCentral(Registry reg) throws MobyException{
-	if(reg == null && defaultCentrals.containsKey("")){
-	    return defaultCentrals.get("");
-	}
-	else if(reg != null && defaultCentrals.containsKey(reg.getEndpoint())){
-	    return defaultCentrals.get(reg.getEndpoint());
-	}
-
-	String className = DEFAULT_CENTRAL_IMPL_CLASSNAME;
-	ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
-	URL resURL = classLoader.getResource("META-INF/"+CENTRAL_IMPL_RESOURCE_NAME);
-	if(resURL != null){
-	    System.err.println("Loading "+resURL);
-	    try{
-		LineNumberReader reader = new LineNumberReader(new InputStreamReader(resURL.openStream()));
-		for(String line = reader.readLine(); line != null; line = reader.readLine()){
-		    if(!line.trim().startsWith("#")){
-			className = line.trim();
-			break;
-		    }
-		}
-	    } catch(Exception e){
-		logger.log(Level.WARNING,
-			   "Error reading " + resURL,
-			   e);
-	    }
-	}
-	try{
-	    System.err.println("Central class is  "+className);
-	    Class clazz = Class.forName(className);
-	    if(reg == null){  // should use default nullary c-tor
-		defaultCentrals.put("", (CentralImpl) clazz.newInstance());
-	    }
-	    else{  // should have (String endpoint, String namespace) c-tor
-		for(Constructor ctor: clazz.getDeclaredConstructors()){
-		    Class[] params = ctor.getParameterTypes();
-		    if(params.length == 2 && params[0].getName().equals("java.lang.String") &&
-		       params[1].getName().equals("java.lang.String") ){
-			defaultCentrals.put(reg.getEndpoint(),
-					    (CentralImpl) ctor.newInstance(reg.getEndpoint(), reg.getNamespace()));
-			break;
-		    }
-		}
-		if(!defaultCentrals.containsKey(reg.getEndpoint())){
-		    logger.log(Level.WARNING,
-			       "Could not find required (String endpoint, String namespace)" +
-			       "constructor for class " + className);
-		}
-	    }
-	} catch(Exception e){
-	    logger.log(Level.WARNING,
-		       "Could not load class " + className,
-		       e);
-	    if(reg == null){
-		defaultCentrals.put("", new CentralImpl());  //fallback to this class, no caching, etc.
-	    }
-	    else{
-		defaultCentrals.put(reg.getEndpoint(), 
-				    new CentralImpl(reg.getEndpoint(), reg.getNamespace()));
-	    }
-	}
-
-	return defaultCentrals.get(reg == null ? "" : reg.getEndpoint());
-    }
-
-    /**
-     * 
-     * @return a String representing the Default mobycentral endpoint. If the
-     *         system property 'moby.check.default' exists and is set to true,
-     *         then the URL http://biomoby.org/mobycentral is queried and the
-     *         default central endpoint is returned, otherwise DEFAULT_ENDPOINT
-     *         is returned.
-     */
-    public static String getDefaultURL() {
-	boolean check = false;
-	try {
-	    check = Boolean.getBoolean("moby.check.default");
-	} catch (Exception e) {
-	    
-	}
-	
-	if (check) {
-	    // return the last checked url if we have done this before
-	    if (CHECKED_URL != null && CHECKED_URL.trim() != "") {
-		return CHECKED_URL; 
-	    }
-	    
-	    // create a HttpClient object
-	    HttpClient client = new HttpClient();
-	    // set up the Head method
-	    HeadMethod method = new HeadMethod("http://biomoby.org/mobycentral");
-	    // do not follow redirects or we will get a 411 error
-	    method.setFollowRedirects(false);
-	    // retry 3 times
-	    method.getParams().setParameter(HttpMethodParams.RETRY_HANDLER,new DefaultHttpMethodRetryHandler(3, false));
-	    // set the user agent ... should probably make this something more reasonable
-	    method.getParams().setParameter(HttpMethodParams.USER_AGENT,"jMoby/1.0");
-	    try {
-		// Execute the method.
-		int statusCode = client.executeMethod(method);
-
-		if (statusCode != HttpStatus.SC_MOVED_PERMANENTLY) {
-		    System.err.println("Method failed: "
-			    + method.getStatusLine());
-		} else {
-		    try {
-			String location = method.getResponseHeader("location").getValue();
-			CHECKED_URL = location;
-			try {
-			    CHECKED_URI = "http://" + (new URL(CHECKED_URL).getAuthority()) + "/MOBY/Central";
-			} catch (MalformedURLException murle ) {
-			    CHECKED_URI = DEFAULT_NAMESPACE;
-			}
-			return CHECKED_URL;
-		    } catch (NullPointerException npe) {
-			return DEFAULT_ENDPOINT;
-		    }
-		}
-	    } catch (HttpException e) {
-		System.err.println("Fatal protocol violation: "
-			+ e.getMessage());
-		e.printStackTrace();
-	    } catch (IOException e) {
-		System.err.println("Fatal transport error: " + e.getMessage());
-		e.printStackTrace();
-	    } finally {
-		// Release the connection.
-		method.releaseConnection();
-	    } 
-	    
-	} else {
-	    return DEFAULT_ENDPOINT;
-	}
-	return DEFAULT_ENDPOINT;
-    }
-    
-    /**
-     * 
-     * @return a String representing the default mobycentral uri. If the
-     *         system property 'moby.check.default' exists and is set to true,
-     *         then the URL http://biomoby.org/mobycentral is queried and the
-     *         default central namespace is returned, otherwise DEFAULT_NAMESPACE
-     *         is returned.
-     */
-    public static String getDefaultURI() {
-	boolean check = false;
-	try {
-	    check = Boolean.getBoolean("moby.check.default");
-	} catch (Exception e) {
-	    
-	}
-	if (check) {
-	    if (CHECKED_URI != null && CHECKED_URI.trim() != "") {
-		return CHECKED_URI; 
-	    }
-	    // need to check ... 
-	    getDefaultURL();
-	    return CHECKED_URI;
-	} else {
-	    return DEFAULT_NAMESPACE;
-	}
-    }
-    /**************************************************************************
-     * Convert non-suitable characters in a XML string into their
-     * entity references. <p>
-     *
-     * <em>Adapted from jDom.</em>
-     *
-     * @param str input to be converted
-     * @return If there were any non-suitable characters, return a new
-     * string with those characters escaped, otherwise return the
-     * unmodified input string
-     *
-     *************************************************************************/
-    public String escapeXML (String str) {
-        StringBuffer buffer = null;
-        char ch;
-        String entity;
-        for (int i = 0; i < str.length(); i++) {
-            ch = str.charAt (i);
-            switch (ch) {
-	    case '<' :
-		entity = "&lt;";
-		break;
-	    case '>' :
-		entity = "&gt;";
-		break;
-	    case '&' :
-		entity = "&amp;";
-		break;
-	    default :
-		entity = null;
-		break;
-            }
-            if (buffer == null) {
-                if (entity != null) {
-                    // An entity occurred, so we'll have to use StringBuffer
-                    // (allocate room for it plus a few more entities).
-                    buffer = new StringBuffer (str.length() + 20);
-                    // Copy previous skipped characters and fall through
-                    // to pickup current character
-                    buffer.append (str.substring (0, i));
-                    buffer.append (entity);
-                }
-            } else {
-                if (entity == null) {
-                    buffer.append (ch);
-                } else {
-                    buffer.append (entity);
-                }
-            }
-        }
-
-        // If there were any entities, return the escaped characters
-        // that we put in the StringBuffer. Otherwise, just return
-        // the unmodified input string.
-        return (buffer == null) ? str : buffer.toString();
-    }
-
-    /*************************************************************************
-     * Format an exception.
-     *************************************************************************/
-    public static String formatFault (AxisFault e, String endpoint, QName method) {
-	ByteArrayOutputStream baos = new ByteArrayOutputStream();
-	formatFault (e, new PrintStream (baos), endpoint, method);
-	return baos.toString();
-    }
-
-    /*************************************************************************
-     * Format an exception.
-     *************************************************************************/
-    public static void formatFault (AxisFault e, PrintStream out,
-				    String endpoint, QName method) {
-	    
-	out.println ("===ERROR===");
-	out.println ("Fault details:");
-	// for some obvious errors I do not print all details (with a lenghty trace stack)
-	String faultString = e.getFaultString();
-	if ( (! faultString.startsWith ("java.net.ConnectException")) &&
-	     (faultString.indexOf ("Could not find class for the service named:") == -1)
-	     ) {
-	    org.w3c.dom.Element[] details = e.getFaultDetails();
-	    for (int i = 0; i < details.length; i++) {
-		String s = details[i].toString().replaceAll ("&lt;", "<");
-		s = s.replaceAll ("&gt;", ">");
-		out.println (s);
-	    }
-	}
-	out.println ("Fault string: " + faultString);
-	out.println ("Fault code:   " + e.getFaultCode());
-	out.println ("Fault actor:  " + e.getFaultActor());
-	if (endpoint != null || method != null)
-	    out.println ("When calling:");
-	if (endpoint != null)
-	    out.println ("\t" + endpoint);
-	if (method != null)
-	    out.println ("\t" + method);
-	out.println ("===========");
-    }
-
-
-}
+// CentralImpl.java
+//    A default client to the Moby Central service.
+//
+//    senger at ebi.ac.uk
+//    February 2003
+//
+
+package org.biomoby.client;
+
+import org.biomoby.registry.meta.Registry;
+import org.biomoby.shared.Central;
+import org.biomoby.shared.MobyData;
+import org.biomoby.shared.MobyDataType;
+import org.biomoby.shared.MobyException;
+import org.biomoby.shared.MobyNamespace;
+import org.biomoby.shared.MobyPrimaryDataSet;
+import org.biomoby.shared.MobyPrimaryDataSimple;
+import org.biomoby.shared.MobyRelationship;
+import org.biomoby.shared.MobySecondaryData;
+import org.biomoby.shared.MobyService;
+import org.biomoby.shared.MobyServiceType;
+import org.biomoby.shared.NoSuccessException;
+import org.biomoby.shared.PendingCurationException;
+import org.biomoby.shared.MobyResourceRef;
+import org.biomoby.shared.Utils;
+
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.namespace.QName;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+import org.apache.axis.AxisFault;
+import org.apache.axis.client.Call;
+import org.apache.axis.client.Service;
+import org.apache.commons.httpclient.DefaultHttpMethodRetryHandler;
+import org.apache.commons.httpclient.Header;
+import org.apache.commons.httpclient.HttpClient;
+import org.apache.commons.httpclient.HttpException;
+import org.apache.commons.httpclient.HttpStatus;
+import org.apache.commons.httpclient.methods.HeadMethod;
+import org.apache.commons.httpclient.params.HttpMethodParams;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.LineNumberReader;
+import java.io.PrintStream;
+import java.io.InputStream;
+import java.lang.reflect.Constructor;
+import java.net.HttpURLConnection;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Vector;
+import java.util.TreeMap;
+import java.util.Comparator;
+import java.util.zip.GZIPInputStream;
+import java.util.logging.*;
+
+/**
+ * A default implementation of the interface {@link org.biomoby.shared.Central Central} allowing access to a Moby
+ * registry.
+ * <p>
+ * This class is supposed to be used by all other clients that wish to communicate with the Moby Registry, but do not
+ * want to know about all XML details that are necessary for talking with the Moby Central directly. This is an example
+ * of a client program:
+ * 
+ * <pre>
+ * import org.biomoby.shared.Central;
+ * import org.biomoby.shared.MobyException;
+ * import org.biomoby.client.CentralImpl;
+ * import java.util.Map;
+ * import java.util.Iterator;
+ * 
+ * public class Test {
+ * 
+ *     public static void main( String[] args ) throws MobyException {
+ * 
+ *         Central worker = new CentralImpl();
+ *         Map authorities = worker.getServiceNamesByAuthority();
+ * 
+ *         for ( Iterator it = authorities.entrySet().iterator(); it.hasNext(); ) {
+ *             Map.Entry entry = ( Map.Entry ) it.next();
+ *             System.out.println( entry.getKey() );
+ *             String[] names = ( String[] ) entry.getValue();
+ *             for ( int i = 0; i &lt; names.length; i++ )
+ *                 System.out.println( &quot;\t&quot; + names[ i ] );
+ *         }
+ *     }
+ * }
+ * </pre>
+ * 
+ * @author <A HREF="mailto:senger at ebi.ac.uk">Martin Senger</A>
+ * @version $Id$
+ */
+
+public class CentralImpl implements Central, SimpleCache {
+
+    private URL endpoint;
+    private String uri;
+    protected boolean debug = false;
+
+    /** Common central used to if getDefaultCentral() is called */
+    protected static Map< String, CentralImpl > defaultCentrals = new HashMap< String, CentralImpl >();
+
+    /** Default location (endpoint) of a Moby registry. */
+    public static final String DEFAULT_ENDPOINT = "http://moby.ucalgary.ca/moby/MOBY-Central.pl";
+
+    /** Default namespace used by the contacted Moby registry. */
+    public static final String DEFAULT_NAMESPACE = "http://moby.ucalgary.ca/MOBY/Central";
+
+    /**
+     * The META-INF resource file that will be checked to determine what default class should be instantiated in order
+     * to create a Central Implementation when getDefaultCentral() is called.
+     */
+    public static final String CENTRAL_IMPL_RESOURCE_NAME = "org.biomoby.shared.CentralDefaultImpl";
+    /** The class to use for getDefaultCentral if all else fails */
+    public static final String DEFAULT_CENTRAL_IMPL_CLASSNAME = "org.biomoby.client.CentralDigestCachedImpl";
+    private static Logger logger = Logger.getLogger( "org.biomoby.client.CentralImpl" );
+
+    /**
+     * Thread local that gives each thread its own DocumentBuilderFactory (since it is not thread-safe). Code taken from
+     * Apache's JaxpUtils.
+     */
+    public static ThreadLocal DOCUMENT_BUILDER_FACTORIES = new ThreadLocal() {
+        protected synchronized Object initialValue() {
+            DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
+            dbf.setNamespaceAware( true );
+            return dbf;
+        }
+    };
+
+    /*******************************************************************************************************************
+     * Default constructor. It connects to a default Moby registry (as defined in {@link #DEFAULT_ENDPOINT}) using a
+     * default namespace (as defined int {@link #DEFAULT_NAMESPACE}).
+     ******************************************************************************************************************/
+    public CentralImpl() throws MobyException {
+        this( DEFAULT_ENDPOINT, DEFAULT_NAMESPACE );
+    }
+
+    /*******************************************************************************************************************
+     * Constructor allowing to specify which Moby Registry to use.
+     * 
+     * @throws MobyException
+     *             if 'endpoint' is not a valid URL, or if no DOM parser is available
+     ******************************************************************************************************************/
+    public CentralImpl( String endpoint ) throws MobyException {
+        this( endpoint, DEFAULT_NAMESPACE );
+    }
+
+    /*******************************************************************************************************************
+     * Constructor allowing to specify which Moby Registry and what namespace to use. If any of the parameters is null,
+     * its default value is used instead.
+     * <p>
+     * 
+     * @throws MobyException
+     *             if 'endpoint' is not a valid URL, or if no DOM parser was found
+     ******************************************************************************************************************/
+    public CentralImpl( String endpoint, String namespace ) throws MobyException {
+
+        if ( endpoint == null || "".equals( endpoint.trim() ) ) endpoint = DEFAULT_ENDPOINT;
+        if ( namespace == null || "".equals( namespace.trim() ) ) namespace = DEFAULT_NAMESPACE;
+
+        try {
+            this.endpoint = new URL( endpoint );
+        }
+        catch ( MalformedURLException e ) {
+            throw new MobyException( "Bad URL: " + endpoint );
+        }
+        this.uri = namespace;
+
+        cache = new Hashtable< String, Object >();
+        useCache = true;
+    }
+
+    /*******************************************************************************************************************
+     * Loads a DOM Document from an InputStream. Uses thread-safe mechanism.
+     ******************************************************************************************************************/
+    public static Document loadDocument( InputStream input ) throws MobyException {
+        try {
+            DocumentBuilderFactory dbf = ( DocumentBuilderFactory ) DOCUMENT_BUILDER_FACTORIES.get();
+            DocumentBuilder db = dbf.newDocumentBuilder();
+            return ( db.parse( input ) );
+        }
+        catch ( Exception e ) {
+            throw new MobyException( "Problem with reading XML input: " + e.toString(), e );
+        }
+    }
+
+    /*******************************************************************************************************************
+     * Call 'method' with 'parameters' and return its result.
+     ******************************************************************************************************************/
+    protected Object doCall( String method, Object[] parameters ) throws MobyException {
+
+        Call call = null;
+        try {
+            Service service = new Service();
+            call = ( Call ) service.createCall();
+            call.setTargetEndpointAddress( endpoint );
+            call.setTimeout( new Integer( 0 ) );
+
+            String user = System.getProperty( "user" );
+            String password = System.getProperty( "password" );
+
+            if ( user != null && password != null ) {
+                call.setProperty( Call.USERNAME_PROPERTY, user );
+                call.setProperty( Call.PASSWORD_PROPERTY, password );
+            }
+
+            call.setSOAPActionURI( uri + "#" + method );
+
+            if ( debug ) {
+                System.err.println( "METHOD CALL: " + method );
+                System.err.println( "------------" );
+                if ( parameters.length > 0 ) System.err.println( parameters[ 0 ] + "\n" );
+                System.err.println( "------------\n" );
+
+                Object result = call.invoke( uri, method, parameters );
+
+                System.err.println( "METHOD RETURN:" );
+                System.err.println( "------------" );
+                if ( result != null ) System.err.println( result + "\n" );
+                System.err.println( "------------\n" );
+
+                return resultToString( result );
+
+            }
+            else {
+                return resultToString( call.invoke( uri, method, parameters ) );
+            }
+
+        }
+        catch ( AxisFault e ) {
+            throw new MobyException( formatFault( e, endpoint.toString(), ( call == null ? null : call
+                    .getOperationName() ) ), e );
+            // (endpoint.toString()+(call == null ? "" : call.getOperationName()), e);
+
+        }
+        catch ( Exception e ) {
+            throw new MobyException( e.toString(), e );
+            // e.printStackTrace();
+        }
+    }
+
+    /*******************************************************************************************************************
+     * Parse the given XML sniplet to find tag 'success'. If it has value '1' look further for tag 'id' and return it
+     * back (or return an empty string if ID is not there). Otherwise raise an exception with the 'culprit' and with the
+     * message from the tag 'message'.
+     * <p>
+     * 
+     * The return value is a two-element long array. The first element is the ID (given by BioMobe registry), and the
+     * second element is RDF corresponding with the registered object (BioMoby returns this only for service instances,
+     * so for other objects this will be null).
+     * <p>
+     * 
+     * This is how the XML is supposed to look: <MOBYRegistration> <success> <!-- 1 | 0 | -1 --> </success> <id> <!--
+     * some id number for your registration --> </id> <message> <![CDATA[message here]]> </message> <RDF> <!-- RDF of
+     * your service instance here (if applicable) --> </RDF> </MOBYRegistration>
+     * 
+     * Success takes the value "1" to indicate success, "0" to indicate failure, and "-1" to indicate "Pending
+     * Curation".
+     ******************************************************************************************************************/
+    protected String[] checkRegistration( String xml, Object culprit ) throws MobyException, NoSuccessException,
+            PendingCurationException {
+
+        String id = "", success = "0", message = "", rdf = "";
+
+        // parse returned XML
+        Document document = loadDocument( new ByteArrayInputStream( xml.getBytes() ) );
+        Element root = document.getDocumentElement();
+
+        NodeList children = root.getChildNodes();
+        for ( int i = 0; i < children.getLength(); i++ ) {
+            if ( children.item( i ).getNodeType() != Node.ELEMENT_NODE ) continue;
+            Element elem = ( Element ) children.item( i );
+            if ( elem.getNodeName().equals( "id" ) ) {
+                if ( elem.getFirstChild() != null ) id = elem.getFirstChild().getNodeValue();
+            }
+            else if ( elem.getNodeName().equals( "success" ) ) {
+                if ( elem.getFirstChild() != null ) success = elem.getFirstChild().getNodeValue();
+            }
+            else if ( elem.getNodeName().equals( "message" ) ) {
+                if ( elem.getFirstChild() != null ) message = elem.getFirstChild().getNodeValue();
+            }
+            else if ( elem.getNodeName().equals( "RDF" ) ) {
+                if ( elem.getFirstChild() != null ) rdf = elem.getFirstChild().getNodeValue();
+            }
+        }
+
+        if ( success.equals( "0" ) )
+            throw new NoSuccessException( message, culprit );
+        else if ( success.equals( "-1" ) ) throw new PendingCurationException();
+        return new String[] { id, rdf };
+    }
+
+    /*******************************************************************************************************************
+     * Return a piece of XML created from the definitions representing input data types and their usage in the given
+     * service. Only data considered primary are included. Note that the main job of converting to XML is done by
+     * instances of MobyPrimaryData.
+     * 
+     * The returned XML looks like this: <Input> <!-- zero or more Primary (Simple and/or Complex) articles --> </Input>
+     ******************************************************************************************************************/
+    protected String buildPrimaryInputTag( MobyService service ) {
+        StringBuffer buf = new StringBuffer();
+        MobyData[] primaryInputs = service.getPrimaryInputs();
+        buf.append( "<Input>\n" );
+        for ( int i = 0; i < primaryInputs.length; i++ )
+            buf.append( primaryInputs[ i ].toXML() );
+        buf.append( "</Input>\n" );
+        return new String( buf );
+    }
+
+    /*******************************************************************************************************************
+     * Return a piece of XML created from the definitions representing input data types and their usage in the given
+     * service. Only data considered secondary are included. Note that the main job of converting to XML is done by
+     * instances of MobySecondaryData.
+     * 
+     * The returned XML looks like this: <secondaryArticles> <!-- zero or more INPUT Secondary articles -->
+     * </secondaryArticles>
+     ******************************************************************************************************************/
+    protected String buildSecondaryInputTag( MobyService service ) {
+        StringBuffer buf = new StringBuffer();
+        MobyData[] secInputs = service.getSecondaryInputs();
+        buf.append( "<secondaryArticles>\n" );
+        for ( int i = 0; i < secInputs.length; i++ ) {
+            buf.append( secInputs[ i ].toXML() );
+        }
+        buf.append( "</secondaryArticles>\n" );
+        return new String( buf );
+    }
+
+    /*******************************************************************************************************************
+     * Return a piece of XML created from the definitions representing output data types and their usage in the given
+     * service. Only data considered primary are included. Note that the main job of converting to XML is done by
+     * instances of MobyPrimaryData.
+     * 
+     * The returned XML looks like this: <Output> <!-- zero or more Primary (Simple and/or Complex) articles -->
+     * </Output>
+     * 
+     ******************************************************************************************************************/
+    protected String buildOutputTag( MobyService service ) {
+        StringBuffer buf = new StringBuffer();
+        MobyData[] primaryOutputs = service.getPrimaryOutputs();
+        buf.append( "<Output>\n" );
+        for ( int i = 0; i < primaryOutputs.length; i++ )
+            buf.append( primaryOutputs[ i ].toXML() );
+        buf.append( "</Output>\n" );
+        return new String( buf );
+    }
+
+    /*******************************************************************************************************************
+     * Return a piece of XML represented a query object (an object used to find a service).
+     * 
+     * The returned XML looks like this:
+     * 
+     * <inputObjects> <Input> <!-- one or more Simple or Complex Primary articles --> </Input> </inputObjects>
+     * <outputObjects> <Output> <!-- one or more Simple or Complex Primary articles --> </Output> </outputObjects>
+     * <serviceType>ServiceTypeTerm</serviceType> <serviceName>ServiceName</serviceName> <Category>moby</Category>
+     * <authURI>http://desired.service.provider</authURI>; <expandObjects>1|0</expandObjects> <expandServices>1|0</expandServices>
+     * <authoritative>1|0</authoritative> <keywords> <keyword>something</keyword> .... .... </keywords>
+     ******************************************************************************************************************/
+    protected String buildQueryObject( MobyService service, String[] keywords, boolean expandObjects,
+            boolean expandServices, boolean authoritative ) {
+        if ( service == null ) {
+            service = new MobyService( "dummy" );
+            service.setCategory( "" );
+        }
+        StringBuffer buf = new StringBuffer();
+
+        buf.append( "<inputObjects>\n<Input>\n" );
+        MobyData[] pi = service.getPrimaryInputs();
+        if ( pi.length > 0 ) {
+            for ( int i = 0; i < pi.length; i++ )
+                buf.append( pi[ i ].toXML() );
+        }
+        buf.append( "</Input>\n</inputObjects>\n" );
+
+        buf.append( "<outputObjects>\n<Output>\n" );
+        MobyData[] po = service.getPrimaryOutputs();
+        if ( po.length > 0 ) {
+            for ( int i = 0; i < po.length; i++ )
+                buf.append( po[ i ].toXML() );
+        }
+        buf.append( "</Output>\n</outputObjects>\n" );
+
+        buf.append( "<serviceType>" + service.getType() + "</serviceType>\n" );
+
+        String name = service.getName();
+        if ( !name.equals( "" ) && !name.equals( "dummy" ) && !name.equals( MobyService.DUMMY_NAME ) )
+            buf.append( "<serviceName>" + service.getName() + "</serviceName>\n" );
+
+        String sigURL = service.getSignatureURL();
+        if ( !sigURL.equals( "" ) ) buf.append( "<signatureURL>" + sigURL + "</signatureURL>\n" );
+
+        buf.append( "<Category>" + service.getCategory() + "</Category>\n" );
+        buf.append( "<authURI>" + service.getAuthority() + "</authURI>\n" );
+
+        buf.append( "<expandObjects>" );
+        buf.append( expandObjects ? "1" : "0" );
+        buf.append( "</expandObjects>\n" );
+
+        buf.append( "<expandServices>" );
+        buf.append( expandServices ? "1" : "0" );
+        buf.append( "</expandServices>\n" );
+
+        buf.append( "<authoritative>" );
+        buf.append( authoritative ? "1" : "0" );
+        buf.append( "</authoritative>\n" );
+
+        buf.append( "<keywords>\n" );
+        if ( keywords != null && keywords.length > 0 ) {
+            for ( int i = 0; i < keywords.length; i++ ) {
+                buf.append( "<keyword>" );
+                buf.append( keywords[ i ] );
+                buf.append( "</keyword>\n" );
+            }
+        }
+        buf.append( "</keywords>\n" );
+
+        return new String( buf );
+    }
+
+    /*******************************************************************************************************************
+     * Extract one or more MobyService objects from the given XML piece. The XML should look like this:
+     * 
+     * <pre>
+     *  &lt;Services&gt;
+     *    &lt;Service authURI=&quot;authority.URI.here&quot; lsid=&quot;...&quot; serviceName=&quot;MyService&quot;&gt;
+     *      &lt;serviceType&gt;Service_Ontology_Term&lt;/serviceType&gt;
+     *      &lt;Category&gt;moby&lt;/Category&gt; &lt;!-- or 'cgi' or 'soap' --&gt;
+     *      &lt;contactEmail&gt;your at email.addy.here&lt;/contactEmail&gt;
+     *      &lt;signatureURL&gt;http://service.RDF.here&lt;/signatureURL&gt;
+     *      &lt;URL&gt;http://service.endpoint.here/scriptname&lt;/URL&gt;
+     *      &lt;authoritative&gt;1&lt;/authoritative&gt;
+     *      &lt;Input&gt;
+     *           &lt;!-- one or more Simple and/or Complex Primary articles --&gt;
+     *      &lt;/Input&gt;
+     *      &lt;Output&gt;
+     *           &lt;!-- one or more Simple and/or Complex Primary articles --&gt; 
+     *      &lt;/Output&gt;
+     *      &lt;secondaryArticles&gt;
+     *           &lt;!-- one or more Secondary articles --&gt;
+     *      &lt;/secondaryArticles&gt;
+     *      &lt;Description&gt;&lt;![CDATA[free text description here]]&gt;&lt;/Description&gt;
+     *    &lt;/Service&gt;
+     *    ...  &lt;!--  one or more Service blocks may be returned --&gt;
+     *    ...
+     *    ...
+     *  &lt;/Services&gt;
+     * </pre>
+     * 
+     * @throws MobyException
+     *             if the XML document is invalid
+     ******************************************************************************************************************/
+    public MobyService[] extractServices( String xml ) throws MobyException {
+
+        Document document = loadDocument( new ByteArrayInputStream( xml.getBytes() ) );
+        NodeList list = document.getElementsByTagName( "Service" );
+        MobyService[] results = new MobyService[ list.getLength() ];
+        for ( int i = 0; i < list.getLength(); i++ ) {
+            Element elem = ( Element ) list.item( i );
+            MobyService service = new MobyService( elem.getAttribute( "serviceName" ) );
+            service.setAuthority( elem.getAttribute( "authURI" ) );
+            service.setLSID( elem.getAttribute( "lsid" ) );
+            NodeList children = elem.getChildNodes();
+            for ( int j = 0; j < children.getLength(); j++ ) {
+                String nodeName = children.item( j ).getNodeName();
+                if ( nodeName.equals( "Description" ) ) {
+                    service.setDescription( getFirstValue( children.item( j ) ) );
+                }
+                else if ( nodeName.equals( "Category" ) ) {
+                    service.setCategory( getFirstValue( children.item( j ) ) );
+                }
+                else if ( nodeName.equals( "URL" ) ) {
+                    service.setURL( getFirstValue( children.item( j ) ) );
+                }
+                else if ( nodeName.equals( "signatureURL" ) ) {
+                    service.setSignatureURL( getFirstValue( children.item( j ) ) );
+                }
+                else if ( nodeName.equals( "contactEmail" ) ) {
+                    service.setEmailContact( getFirstValue( children.item( j ) ) );
+                }
+                else if ( nodeName.equals( "serviceType" ) ) {
+                    service.setType( getFirstValue( children.item( j ) ) );
+                    MobyServiceType mst = new MobyServiceType( service.getType() );
+                    NamedNodeMap map = ( children.item( j ).getAttributes() );
+                    if ( map != null ) {
+                        Node node = map.getNamedItemNS( children.item( j ).getNamespaceURI(), "lsid" );
+                        if ( node != null ) mst.setLSID( node.getNodeValue() );
+                    }
+                    service.setServiceType( mst );
+                }
+                else if ( nodeName.equals( "authoritative" ) ) {
+                    String authoritative = getFirstValue( children.item( j ) );
+                    service.setAuthoritative( authoritative.equals( "1" ) ? true : false );
+                }
+                else if ( nodeName.equals( "Input" ) ) {
+                    // <Input>
+                    // <!-- one or more Simple and/or Complex Primary articles -->
+                    // <Simple articleName="NameOfArticle">
+                    // ...
+                    // </Simple>
+                    // <Collection articleName="NameOfArticle">
+                    // <Simple>......</Simple>
+                    // <Simple>......</Simple>
+                    // </Collection>
+                    // </Input>
+                    NodeList inputs = children.item( j ).getChildNodes();
+                    for ( int k = 0; k < inputs.getLength(); k++ ) {
+                        if ( inputs.item( k ).getNodeName().equals( "Simple" ) ) {
+                            MobyPrimaryDataSimple data = new MobyPrimaryDataSimple( ( Element ) inputs.item( k ) );
+                            service.addInput( data );
+                        }
+                        else if ( inputs.item( k ).getNodeName().equals( "Collection" ) ) {
+                            MobyPrimaryDataSet data = new MobyPrimaryDataSet( ( Element ) inputs.item( k ) );
+                            service.addInput( data );
+                        }
+                    }
+                }
+                else if ( nodeName.equals( "Output" ) ) {
+                    // <Output>
+                    // <!-- one or more Simple and/or Complex Primary articles -->
+                    // </Output>
+                    NodeList inputs = children.item( j ).getChildNodes();
+                    for ( int k = 0; k < inputs.getLength(); k++ ) {
+                        if ( inputs.item( k ).getNodeName().equals( "Simple" ) ) {
+                            MobyPrimaryDataSimple data = new MobyPrimaryDataSimple( ( Element ) inputs.item( k ) );
+                            service.addOutput( data );
+                        }
+                        else if ( inputs.item( k ).getNodeName().equals( "Collection" ) ) {
+                            MobyPrimaryDataSet data = new MobyPrimaryDataSet( ( Element ) inputs.item( k ) );
+                            service.addOutput( data );
+                        }
+                    }
+
+                }
+                else if ( nodeName.equals( "secondaryArticles" ) ) {
+                    // <Parameter articleName="NameOfArticle">
+                    // ...
+                    // </Parameter>
+                    NodeList parameters = children.item( j ).getChildNodes();
+                    for ( int k = 0; k < parameters.getLength(); k++ ) {
+                        if ( parameters.item( k ).getNodeName().equals( "Parameter" ) ) {
+                            MobySecondaryData data = new MobySecondaryData( ( Element ) parameters.item( k ) );
+                            service.addInput( data );
+                        }
+                    }
+                }
+            }
+            results[ i ] = service;
+        }
+        return results;
+    }
+
+    // protect against null values
+    protected String getFirstValue( Node child ) {
+        Node node = child.getFirstChild();
+        if ( node == null ) return "";
+        String value = node.getNodeValue();
+        if ( value == null ) return "";
+        return value;
+    }
+
+    protected String getFirstValue( NodeList children ) {
+        if ( children.item( 0 ) != null && children.item( 0 ).hasChildNodes() ) {
+            children.item( 0 ).normalize();
+            return getFirstValue( children.item( 0 ) );
+        }
+        return "";
+    }
+
+    /*******************************************************************************************************************
+     * 
+     * Implementing SimpleCache interface.
+     * 
+     * Why to have an interface for such trivial thing? Well, because I needed to overwrite the caching mechanism in the
+     * subclasses so I needed to have all caching functions as separate methods - that's why I have collect them in an
+     * interface.
+     * 
+     ******************************************************************************************************************/
+    private Hashtable< String, Object > cache; // this is the cache itself
+    private boolean useCache; // this signal that we are actually caching things
+
+    // not used here
+    public String createId( String rootName, String semanticType, String syntaxType, long lastModified, Properties props ) {
+        return ""; // not used here
+    }
+
+    // check existence of a cached object
+    public boolean existsInCache( String id ) {
+        synchronized ( cache ) {
+            if ( useCache )
+                return cache.containsKey( id );
+            else
+                return false;
+        }
+    }
+
+    // retrieve from cache
+    public Object getContents( String id ) {
+        synchronized ( cache ) {
+            if ( useCache )
+                return cache.get( id );
+            else
+                return null;
+        }
+    }
+
+    // cache an object
+    public void setContents( String id, java.lang.Object data ) {
+        synchronized ( cache ) {
+            if ( useCache ) cache.put( id, data );
+        }
+    }
+
+    // in this implementation, it clears the whole cache, regardless
+    // what 'id' is passed
+    public void removeFromCache( String id ) {
+        cache.clear();
+    }
+
+    /*******************************************************************************************************************
+     * 
+     * And the other methods related to caching (but not part of the SimpleCache interface).
+     * 
+     ******************************************************************************************************************/
+
+    /*******************************************************************************************************************
+     * By default, caching is enabled to reduce network traffic. Setting this to false will clear the cache, and not
+     * cache any further calls unless it is set to true again.
+     * <p>
+     * 
+     * @param shouldCache
+     *            whether retrieveXXX call results should be cached in case they are called again (i.e. don't request
+     *            MobyCentral every time)
+     ******************************************************************************************************************/
+    public void setCacheMode( boolean shouldCache ) {
+        useCache = shouldCache;
+        if ( !useCache ) removeFromCache( null );
+    }
+
+    /*******************************************************************************************************************
+     * Find if caching is currently enabled.
+     * 
+     * @return true if caching is enabled
+     ******************************************************************************************************************/
+    public boolean getCacheMode() {
+        return useCache;
+    }
+
+    /*******************************************************************************************************************
+     * Parses and imports the following XML.
+     * 
+     * <pre>
+     * &lt;serviceNames&gt;
+     *   &lt;serviceName name=&quot;serviceName&quot; authURI='authority.info.here'/&gt;
+     *   ...
+     *   ...
+     * &lt;/serviceNames&gt;
+     * </pre>
+     * 
+     * @deprecated Replaced by {@link #getServiceNamesByAuthority}. The reason is that this method returns a random
+     *             result if there are more services with the same name but belonging to different authorities.
+     *             <p>
+     * 
+     ******************************************************************************************************************/
+    public Map< String, String > getServiceNames() throws MobyException {
+
+        String result = ( String ) doCall( "retrieveServiceNames", new Object[] {} );
+        // parse returned XML
+        Map< String, String > results = new TreeMap< String, String >( getStringComparator() );
+        Document document = loadDocument( new ByteArrayInputStream( result.getBytes() ) );
+        NodeList list = document.getElementsByTagName( "serviceName" );
+        for ( int i = 0; i < list.getLength(); i++ ) {
+            Element elem = ( Element ) list.item( i );
+            results.put( elem.getAttribute( "name" ), elem.getAttribute( "authURI" ) );
+        }
+
+        return results;
+    }
+
+    /*******************************************************************************************************************
+     * Parses and imports the following XML.
+     * 
+     * <pre>
+     * &lt;serviceNames&gt;
+     *   &lt;serviceName name=&quot;serviceName&quot; lsid=&quot;...&quot; authURI='authority.info.here'/&gt;
+     *   ...
+     *   ...
+     * &lt;/serviceNames&gt;
+     * </pre>
+     * 
+     * @return a Map which has authorities as keys, and String arrays with service names as a values.
+     ******************************************************************************************************************/
+    public Map getServiceNamesByAuthority() throws MobyException {
+        String result = getServiceNamesByAuthorityAsXML();
+        return createServicesByAuthorityFromXML( result, true );
+    }
+
+    /*******************************************************************************************************************
+     * Similar to {@link #getServiceNamesByAuthority} but the resulting Map contains slightly more.
+     * <p>
+     * 
+     * @return a Map which has authorities as keys, and arrays of MobyServices as a values. Each MobyService is filled
+     *         with its name, authority and LSID.
+     ******************************************************************************************************************/
+    public Map getServicesByAuthority() throws MobyException {
+        String result = getServiceNamesByAuthorityAsXML();
+        return createServicesByAuthorityFromXML( result, false );
+    }
+
+    //
+    protected String getServiceNamesByAuthorityAsXML() throws MobyException {
+        return ( String ) doCall( "retrieveServiceNames", new Object[] {} );
+    }
+
+    // if onlyNames == true
+    // Map: authority name -> String[]
+    // (filled with service namea)
+    // else
+    // Map: authority name -> MobyService[]
+    // (filled with service name, authority and lsid)
+    protected Map createServicesByAuthorityFromXML( String result, boolean onlyNames ) throws MobyException {
+
+        // parse returned XML
+        Map results = new TreeMap( getStringComparator() );
+        Document document = loadDocument( new ByteArrayInputStream( result.getBytes() ) );
+        NodeList list = document.getElementsByTagName( "serviceName" );
+        for ( int i = 0; i < list.getLength(); i++ ) {
+            Element elem = ( Element ) list.item( i );
+            String name = elem.getAttribute( "name" );
+            String auth = elem.getAttribute( "authURI" );
+            Vector< Object > v = ( results.containsKey( auth ) ? ( Vector ) results.get( auth )
+                    : new Vector< Object >() );
+            if ( onlyNames ) {
+                v.addElement( name );
+            }
+            else {
+                MobyService ms = new MobyService( name );
+                ms.setAuthority( auth );
+                ms.setLSID( elem.getAttribute( "lsid" ) );
+                v.addElement( ms );
+            }
+            results.put( auth, v );
+        }
+
+        // change values of type Vector to MobyService[] or String[]
+        for ( Iterator it = results.entrySet().iterator(); it.hasNext(); ) {
+            Map.Entry entry = ( Map.Entry ) it.next();
+            Vector v = ( Vector ) entry.getValue();
+            if ( onlyNames ) {
+                String[] sNames = new String[ v.size() ];
+                v.copyInto( sNames );
+                entry.setValue( sNames );
+            }
+            else {
+                MobyService[] mss = new MobyService[ v.size() ];
+                v.copyInto( mss );
+                entry.setValue( mss );
+            }
+        }
+
+        return results;
+    }
+
+    /*******************************************************************************************************************
+     * Parses and imports the following XML.
+     * 
+     * <pre>
+     *  &lt;serviceProviders&gt;
+     *     &lt;serviceProvider name=&quot;authority.URI.here&quot;/&gt;
+     *          ...
+     *          ...
+     *  &lt;/serviceProviders&gt;
+     * </pre>
+     ******************************************************************************************************************/
+    public String[] getProviders() throws MobyException {
+
+        String cacheId = "retrieveServiceProviders";
+        String[] cachedResults = ( String[] ) getContents( cacheId );
+        if ( cachedResults != null ) return cachedResults;
+
+        String result = ( String ) doCall( "retrieveServiceProviders", new Object[] {} );
+
+        // parse returned XML
+        Document document = loadDocument( new ByteArrayInputStream( result.getBytes() ) );
+        NodeList list = document.getElementsByTagName( "serviceProvider" );
+        String[] results = new String[ list.getLength() ];
+        for ( int i = 0; i < list.getLength(); i++ )
+            results[ i ] = ( ( Element ) list.item( i ) ).getAttribute( "name" );
+
+        // Add this data to the cache in case we get called again
+        setContents( cacheId, results );
+
+        return results;
+    }
+
+    /*******************************************************************************************************************
+     * Parses and imports the following XML.
+     * 
+     * <pre>
+     *  &lt;serviceTypes&gt;
+     *     &lt;serviceType name=&quot;serviceName&quot; lsid=&quot;...&quot;&gt;
+     *            &lt;Description&gt;&lt;![CDATA[free text description here]]&gt;&lt;/Description&gt;
+     *            &lt;contactEmail&gt;...&lt;/contactEmail&gt;
+     *            &lt;authURI&gt;...&lt;/authURI&gt;
+     *     &lt;/serviceType&gt;
+     *          ...
+     *          ...
+     *  &lt;/serviceTypes&gt;
+     * </pre>
+     ******************************************************************************************************************/
+    public Map getServiceTypes() throws MobyException {
+        String result = getServiceTypesAsXML();
+        Map results = new TreeMap( getStringComparator() );
+        MobyServiceType[] types = createServiceTypesFromXML( result );
+        for ( int i = 0; i < types.length; i++ ) {
+            results.put( types[ i ].getName(), types[ i ].getDescription() );
+        }
+        return results;
+    }
+
+    //
+    protected String getServiceTypesAsXML() throws MobyException {
+        return ( String ) doCall( "retrieveServiceTypes", new Object[] {} );
+    }
+
+    // but be aware that the created MobyServiceTypes are not complete
+    // - they do not have the relationship information; that's why
+    // this method is not public; the full service types are available
+    // from CentralDigest implementations
+    protected MobyServiceType[] createServiceTypesFromXML( String result ) throws MobyException {
+
+        // parse returned XML
+        Document document = loadDocument( new ByteArrayInputStream( result.getBytes() ) );
+        NodeList list = document.getElementsByTagName( "serviceType" );
+        if ( list == null || list.getLength() == 0 ) return new MobyServiceType[] {};
+        MobyServiceType[] results = new MobyServiceType[ list.getLength() ];
+        for ( int i = 0; i < list.getLength(); i++ ) {
+            Element elem = ( Element ) list.item( i );
+            MobyServiceType st = new MobyServiceType( elem.getAttribute( "name" ) );
+            st.setLSID( elem.getAttribute( "lsid" ) );
+            st.setDescription( getFirstValue( elem.getElementsByTagName( "Description" ) ) );
+            st.setEmailContact( getFirstValue( elem.getElementsByTagName( "contactEmail" ) ) );
+            st.setAuthority( getFirstValue( elem.getElementsByTagName( "authURI" ) ) );
+            results[ i ] = st;
+        }
+        java.util.Arrays.sort( results );
+        return results;
+    }
+
+    /*******************************************************************************************************************
+     * Parses and imports the following XML.
+     * 
+     * <pre>
+     *  &lt;Namespaces&gt;
+     *     &lt;Namespace name=&quot;namespace&quot; lsid=&quot;...&quot;&gt;
+     *            &lt;Description&gt;&lt;![CDATA[free text description here]]&gt;&lt;/Description&gt;
+     *            &lt;contactEmail&gt;...&lt;/contactEmail&gt;
+     *            &lt;authURI&gt;...&lt;/authURI&gt;
+     *     &lt;/Namespace&gt;
+     *          ...
+     *          ...
+     *  &lt;/Namespaces&gt;
+     * </pre>
+     ******************************************************************************************************************/
+    public MobyNamespace[] getFullNamespaces() throws MobyException {
+
+        String result = getNamespacesAsXML();
+        return createNamespacesFromXML( result );
+    }
+
+    //
+    protected String getNamespacesAsXML() throws MobyException {
+        return ( String ) doCall( "retrieveNamespaces", new Object[] {} );
+    }
+
+    //
+    protected MobyNamespace[] createNamespacesFromXML( String result ) throws MobyException {
+
+        // parse returned XML
+        Document document = loadDocument( new ByteArrayInputStream( result.getBytes() ) );
+        NodeList list = document.getDocumentElement().getElementsByTagName( "Namespace" );
+        if ( list == null || list.getLength() == 0 ) {
+            return new MobyNamespace[] {};
+        }
+        MobyNamespace[] results = new MobyNamespace[ list.getLength() ];
+        for ( int i = 0; i < list.getLength(); i++ ) {
+            Element elem = ( Element ) list.item( i );
+            MobyNamespace nm = new MobyNamespace( elem.getAttribute( "name" ) );
+            nm.setLSID( elem.getAttribute( "lsid" ) );
+            nm.setDescription( getFirstValue( elem.getElementsByTagName( "Description" ) ) );
+            nm.setEmailContact( getFirstValue( elem.getElementsByTagName( "contactEmail" ) ) );
+            nm.setAuthority( getFirstValue( elem.getElementsByTagName( "authURI" ) ) );
+            results[ i ] = nm;
+        }
+
+        java.util.Arrays.sort( results );
+        return results;
+    }
+
+    /*******************************************************************************************************************
+     * 
+     * @deprecated Replaced by {@link #getFullNamespaces} that gives more information for the same price.
+     *             <p>
+     ******************************************************************************************************************/
+    public Map getNamespaces() throws MobyException {
+
+        Map results = new TreeMap( getStringComparator() );
+        MobyNamespace[] namespaces = getFullNamespaces();
+        for ( int i = 0; i < namespaces.length; i++ ) {
+            results.put( namespaces[ i ].getName(), namespaces[ i ].getDescription() );
+        }
+        return results;
+    }
+
+    /*******************************************************************************************************************
+     * Parses and imports the following XML.
+     * 
+     * <pre>
+     *  &lt;objectNames&gt;
+     *     &lt;Object name=&quot;objectName&quot; lsid=&quot;...&quot;&gt;
+     *            &lt;Description&gt;&lt;![CDATA[free text description here]]&gt;&lt;/Description&gt;
+     *     &lt;/Object&gt;
+     *          ...
+     *          ...
+     *  &lt;/objectNames&gt;
+     * </pre>
+     ******************************************************************************************************************/
+    public Map getDataTypeNames() throws MobyException {
+        String result = getDataTypeNamesAsXML();
+        return createDataTypeNamesFromXML( result, true );
+    }
+
+    //
+    protected String getDataTypeNamesAsXML() throws MobyException {
+        return ( String ) doCall( "retrieveObjectNames", new Object[] {} );
+    }
+
+    // if onlyNames == true
+    // Map: data type name -> description (String)
+    // else
+    // Map: data type name -> MobyDataType[]
+    // (filled with name, description, and lsid)
+    protected Map createDataTypeNamesFromXML( String result, boolean onlyNames ) throws MobyException {
+
+        // parse returned XML
+        Map results = new TreeMap( getStringComparator() );
+        Document document = loadDocument( new ByteArrayInputStream( result.getBytes() ) );
+        NodeList list = document.getElementsByTagName( "Object" );
+        for ( int i = 0; i < list.getLength(); i++ ) {
+            Element elem = ( Element ) list.item( i );
+            String name = elem.getAttribute( "name" );
+            if ( name == null ) continue; // ignore no-named data types
+            String desc = "";
+            NodeList children = elem.getChildNodes();
+            for ( int j = 0; j < children.getLength(); j++ ) {
+                if ( children.item( j ).getNodeName().equals( "Description" ) ) {
+                    desc = getFirstValue( children.item( j ) );
+                    break;
+                }
+            }
+            if ( onlyNames ) {
+                results.put( name, desc );
+            }
+            else {
+                MobyDataType dt = new MobyDataType( name );
+                dt.setDescription( desc );
+                dt.setLSID( elem.getAttribute( "lsid" ) );
+                results.put( name, dt );
+            }
+        }
+        return results;
+    }
+
+    /*******************************************************************************************************************
+     * Parses and imports the following XML. An example:
+     * 
+     * <pre>
+     * &lt;retrieveObjectDefinition&gt;
+     *   &lt;objectType lsid=&quot;...&quot;&gt;go_term&lt;/objectType&gt;
+     *   &lt;Description&gt;&lt;![CDATA[A very lightweight object holding a GO term name and its definition]]&gt;&lt;/Description&gt;
+     *   &lt;authURI&gt;http://www.illuminae.com&lt;/authURI&gt;
+     *   &lt;contactEmail&gt;markw at illuminae.com&lt;/contactEmail&gt;
+     *   &lt;Relationship relationshipType='urn:lsid:biomoby.org:objectrelation:isa'&gt;
+     *      &lt;objectType articleName=''&gt;urn:lsid:biomoby.org:objectclass:object&lt;/objectType&gt;
+     *   &lt;/Relationship&gt;
+     *   &lt;Relationship relationshipType='urn:lsid:biomoby.org:objectrelation:hasa'&gt;
+     *      &lt;objectType articleName='Term'&gt;urn:lsid:biomoby.org:objectclass:string&lt;/objectType&gt;
+     *      &lt;objectType articleName='Definition'&gt;urn:lsid:biomoby.org:objectclass:string&lt;/objectType&gt;
+     *   &lt;/Relationship&gt;
+     *   &lt;Relationship relationshipType='urn:lsid:biomoby.org:objectrelation:has'&gt;
+     *      &lt;objectType articleName='Problems'&gt;urn:lsid:biomoby.org:objectclass:string&lt;/objectType&gt;
+     *      &lt;objectType articleName='Issues'&gt;urn:lsid:biomoby.org:objectclass:string&lt;/objectType&gt;
+     *   &lt;/Relationship&gt;
+     * &lt;/retrieveObjectDefinition&gt;
+     * </pre>
+     ******************************************************************************************************************/
+    public MobyDataType getDataType( String dataTypeName ) throws MobyException, NoSuccessException {
+
+        String result = getDataTypeAsXML( dataTypeName );
+        return createDataTypeFromXML( result, dataTypeName );
+    }
+
+    public MobyDataType[] getDataTypes() throws MobyException, NoSuccessException {
+        Map< String, String > datatypeMap = getDataTypeNames();
+        MobyDataType[] datatypes = new MobyDataType[ datatypeMap.size() ];
+        int i = 0;
+        for ( String dataTypeName : datatypeMap.keySet() ) {
+            datatypes[ i++ ] = getDataType( dataTypeName );
+        }
+        return datatypes;
+    }
+
+    protected String getDataTypeAsXML( String dataTypeName ) throws MobyException, NoSuccessException {
+
+        return ( String ) doCall( "retrieveObjectDefinition", new Object[] { "<retrieveObjectDefinition>"
+                + "<objectType>" + dataTypeName + "</objectType>" + "</retrieveObjectDefinition>" } );
+    }
+
+    protected MobyDataType createDataTypeFromXML( String xmlSource, String dataTypeName ) throws MobyException,
+            NoSuccessException {
+
+        // parse returned XML
+        Document document = loadDocument( new ByteArrayInputStream( xmlSource.getBytes() ) );
+        NodeList list = document.getElementsByTagName( "retrieveObjectDefinition" );
+        if ( list == null || list.getLength() == 0 )
+            throw new NoSuccessException( "Data Type name was not found.", dataTypeName );
+        MobyDataType data = null;
+        Element elem = ( Element ) list.item( 0 );
+        NodeList children = elem.getChildNodes();
+
+        // first find the "real" (LSID-ized) data type name
+        for ( int j = 0; j < children.getLength(); j++ ) {
+            String nodeName = children.item( j ).getNodeName();
+            if ( nodeName.equals( "objectType" ) ) {
+                data = new MobyDataType( getFirstValue( children.item( j ) ) );
+                data.setLSID( ( ( Element ) children.item( j ) ).getAttribute( "lsid" ) );
+                break;
+            }
+        }
+
+        // if not found (unprobable) use the name given by the caller
+        if ( data == null ) data = new MobyDataType( dataTypeName );
+
+        // now fill the data type object with the rest of attributes
+        for ( int j = 0; j < children.getLength(); j++ ) {
+            String nodeName = children.item( j ).getNodeName();
+            if ( nodeName.equals( "Description" ) ) {
+                data.setDescription( getFirstValue( children.item( j ) ) );
+            }
+            else if ( nodeName.equals( "authURI" ) ) {
+                data.setAuthority( getFirstValue( children.item( j ) ) );
+            }
+            else if ( nodeName.equals( "contactEmail" ) ) {
+                data.setEmailContact( getFirstValue( children.item( j ) ) );
+            }
+            else if ( nodeName.equals( "Relationship" ) ) {
+                String relationshipType = ( ( Element ) children.item( j ) ).getAttribute( "relationshipType" );
+                if ( relationshipType.endsWith( "isa" ) ) {
+
+                    NodeList parents = children.item( j ).getChildNodes();
+                    for ( int k = 0; k < parents.getLength(); k++ ) {
+                        if ( parents.item( k ).getNodeName().equals( "objectType" ) ) {
+                            data.addParentName( getFirstValue( parents.item( k ) ) );
+                        }
+                    }
+                }
+                else if ( relationshipType.endsWith( "hasa" ) ) {
+
+                    NodeList belows = children.item( j ).getChildNodes();
+                    for ( int k = 0; k < belows.getLength(); k++ ) {
+                        if ( belows.item( k ).getNodeName().equals( "objectType" ) ) {
+                            data.addChild( ( ( Element ) belows.item( k ) ).getAttribute( "articleName" ),
+                                    getFirstValue( belows.item( k ) ), Central.iHASA );
+                        }
+                    }
+                }
+                else if ( relationshipType.endsWith( "has" ) ) {
+
+                    NodeList belows = children.item( j ).getChildNodes();
+                    for ( int k = 0; k < belows.getLength(); k++ ) {
+                        if ( belows.item( k ).getNodeName().equals( "objectType" ) ) {
+                            data.addChild( ( ( Element ) belows.item( k ) ).getAttribute( "articleName" ), belows.item(
+                                    k ).getFirstChild().getNodeValue(), Central.iHAS );
+                        }
+                    }
+                }
+            }
+        }
+        return data;
+    }
+
+    /*******************************************************************************************************************
+     * 
+     ******************************************************************************************************************/
+    public String getServiceWSDL( String serviceName ) throws MobyException, NoSuccessException {
+
+        Map names = getServiceNames();
+
+        for ( Iterator it = names.entrySet().iterator(); it.hasNext(); ) {
+            Map.Entry entry = ( Map.Entry ) it.next();
+            if ( ( ( String ) entry.getKey() ).equals( serviceName ) )
+                return getServiceWSDL( serviceName, ( String ) entry.getValue() );
+        }
+
+        throw new NoSuccessException( "Service not found.", serviceName );
+    }
+
+    /*******************************************************************************************************************
+     * 
+     ******************************************************************************************************************/
+    public String getServiceWSDL( String serviceName, String authority ) throws MobyException, NoSuccessException {
+
+        String cacheId = "getServiceWSDL" + serviceName + ":" + authority;
+        String cachedResults = ( String ) getContents( cacheId );
+        if ( cachedResults != null ) return cachedResults;
+
+        String result = ( String ) doCall( "retrieveService",
+                new Object[] { "<retrieveService>" + "<Service authURI=\"" + authority + "\" serviceName=\""
+                        + serviceName + "\"/>" + "</retrieveService>" } );
+
+        // parse returned XML
+        Document document = loadDocument( new ByteArrayInputStream( result.getBytes() ) );
+        Element service = document.getDocumentElement();
+        Node wsdl = service.getFirstChild();
+        if ( wsdl == null )
+            throw new NoSuccessException( "Service not found OR WSDL is not available.", serviceName + " (" + authority
+                    + ")" );
+
+        String results = wsdl.getNodeValue();
+        setContents( cacheId, results );
+        return results;
+    }
+
+    /*******************************************************************************************************************
+     * 
+     ******************************************************************************************************************/
+    public String getRegisterDataTypeXML( MobyDataType dataType ) {
+
+        // build the ISA tag (expressing hierarchy of data types)
+        String[] names = dataType.getParentNames();
+        StringBuffer buf = new StringBuffer();
+        for ( int i = 0; i < names.length; i++ ) {
+            buf.append( "<objectType>" );
+            buf.append( names[ i ] );
+            buf.append( "</objectType>" );
+            buf.append( "\n" );
+        }
+
+        // build the HASA/HAS tags (expressing containments of data types)
+        MobyRelationship[] children = dataType.getChildren();
+        StringBuffer buf2 = new StringBuffer(); // for HASA
+        StringBuffer buf3 = new StringBuffer(); // for HAS
+        for ( int i = 0; i < children.length; i++ ) {
+            if ( children[ i ].getRelationshipType() == Central.iHASA ) {
+                buf2.append( "<objectType articleName=\"" );
+                buf2.append( children[ i ].getName() );
+                buf2.append( "\">" );
+                buf2.append( children[ i ].getDataTypeName() );
+                buf2.append( "</objectType>" );
+            }
+            else if ( children[ i ].getRelationshipType() == Central.iHAS ) {
+                buf3.append( "<objectType articleName=\"" );
+                buf3.append( children[ i ].getName() );
+                buf3.append( "\">" );
+                buf3.append( children[ i ].getDataTypeName() );
+                buf3.append( "</objectType>" );
+            }
+        }
+
+        return "<registerObjectClass>" + "<objectType>" + dataType.getName() + "</objectType>"
+                + "<Description><![CDATA[" + dataType.getDescription() + "]]>" + "</Description>"
+                + "<Relationship relationshipType=\"ISA\">" + new String( buf ) + "</Relationship>"
+                + "<Relationship relationshipType=\"HASA\">" + new String( buf2 ) + "</Relationship>"
+                + "<Relationship relationshipType=\"HAS\">" + new String( buf3 ) + "</Relationship>" + "<authURI>"
+                + dataType.getAuthority() + "</authURI>" + "<contactEmail>" + dataType.getEmailContact()
+                + "</contactEmail>" + "</registerObjectClass>";
+    }
+
+    /*******************************************************************************************************************
+     * 
+     ******************************************************************************************************************/
+    public void registerDataType( MobyDataType dataType ) throws MobyException, NoSuccessException,
+            PendingCurationException {
+
+        String result = ( String ) doCall( "registerObjectClass", new Object[] { getRegisterDataTypeXML( dataType ) } );
+        dataType.setId( checkRegistration( result, dataType )[ 0 ] );
+    }
+
+    /*******************************************************************************************************************
+     * B
+     ******************************************************************************************************************/
+    public void unregisterDataType( MobyDataType dataType ) throws MobyException, NoSuccessException,
+            PendingCurationException {
+        String result = ( String ) doCall( "deregisterObjectClass", new Object[] { "<deregisterObjectClass>"
+                + "<objectType>" + dataType.getName() + "</objectType>" + "</deregisterObjectClass>" } );
+        checkRegistration( result, dataType );
+    }
+
+    /*******************************************************************************************************************
+     * 
+     ******************************************************************************************************************/
+    public String getRegisterServiceTypeXML( MobyServiceType serviceType ) {
+
+        // build the ISA tag (expressing hierarchy of service types)
+        String[] names = serviceType.getParentNames();
+        StringBuffer buf = new StringBuffer();
+        for ( int i = 0; i < names.length; i++ ) {
+            buf.append( "<serviceType>" );
+            buf.append( names[ i ] );
+            buf.append( "</serviceType>" );
+            buf.append( "\n" );
+        }
+
+        return "<registerServiceType>" + "<serviceType>" + serviceType.getName() + "</serviceType>" + "<contactEmail>"
+                + serviceType.getEmailContact() + "</contactEmail>" + "<authURI>" + serviceType.getAuthority()
+                + "</authURI>" + "<Description><![CDATA[" + serviceType.getDescription() + "]]>" + "</Description>"
+                + "<Relationship relationshipType=\"ISA\">" + new String( buf ) + "</Relationship>"
+                + "</registerServiceType>";
+    }
+
+    /*******************************************************************************************************************
+     * 
+     ******************************************************************************************************************/
+    public void registerServiceType( MobyServiceType serviceType ) throws MobyException, NoSuccessException,
+            PendingCurationException {
+
+        String result = ( String ) doCall( "registerServiceType",
+                new Object[] { getRegisterServiceTypeXML( serviceType ) } );
+        serviceType.setId( checkRegistration( result, serviceType )[ 0 ] );
+    }
+
+    /*******************************************************************************************************************
+     * 
+     ******************************************************************************************************************/
+    public void unregisterServiceType( MobyServiceType serviceType ) throws MobyException, NoSuccessException,
+            PendingCurationException {
+        String result = ( String ) doCall( "deregisterServiceType", new Object[] { "<deregisterServiceType>"
+                + "<serviceType>" + serviceType.getName() + "</serviceType>" + "</deregisterServiceType>" } );
+        checkRegistration( result, serviceType );
+    }
+
+    /*******************************************************************************************************************
+     * 
+     ******************************************************************************************************************/
+    public String getRegisterNamespaceXML( MobyNamespace namespace ) {
+        return "<registerNamespace>" + "<namespaceType>" + namespace.getName() + "</namespaceType>" + "<contactEmail>"
+                + namespace.getEmailContact() + "</contactEmail>" + "<authURI>" + namespace.getAuthority()
+                + "</authURI>" + "<Description><![CDATA[" + namespace.getDescription() + "]]>" + "</Description>"
+                + "</registerNamespace>";
+    }
+
+    /*******************************************************************************************************************
+     * 
+     ******************************************************************************************************************/
+    public void registerNamespace( MobyNamespace namespace ) throws MobyException, NoSuccessException,
+            PendingCurationException {
+        String result = ( String ) doCall( "registerNamespace", new Object[] { getRegisterNamespaceXML( namespace ) } );
+        namespace.setId( checkRegistration( result, namespace )[ 0 ] );
+    }
+
+    /*******************************************************************************************************************
+     * 
+     ******************************************************************************************************************/
+    public void unregisterNamespace( MobyNamespace namespace ) throws MobyException, NoSuccessException,
+            PendingCurationException {
+        String result = ( String ) doCall( "deregisterNamespace", new Object[] { "<deregisterNamespace>"
+                + "<namespaceType>" + namespace.getName() + "</namespaceType>" + "</deregisterNamespace>" } );
+        checkRegistration( result, namespace );
+    }
+
+    /*******************************************************************************************************************
+     * 
+     ******************************************************************************************************************/
+    public String getRegisterServiceXML( MobyService service ) {
+        return "<registerService>" + "<Category>" + service.getCategory() + "</Category>" + "<serviceName>"
+                + service.getName() + "</serviceName>" + "<serviceType>" + service.getType() + "</serviceType>"
+                + "<serviceLSID>" + ( service.getLSID() == null ? "" : service.getLSID().trim() ) + "</serviceLSID>"
+                + "<authURI>" + service.getAuthority() + "</authURI>" + "<signatureURL>"
+                + escapeXML( service.getSignatureURL() ) + "</signatureURL>" + "<URL>" + escapeXML( service.getURL() )
+                + "</URL>" + "<contactEmail>" + service.getEmailContact() + "</contactEmail>"
+                + "<authoritativeService>" + ( service.isAuthoritative() ? "1" : "0" ) + "</authoritativeService>"
+                + "<Description><![CDATA[" + service.getDescription() + "]]>" + "</Description>"
+                + buildPrimaryInputTag( service ) + buildSecondaryInputTag( service ) + buildOutputTag( service )
+                + "</registerService>";
+    }
+
+    /*******************************************************************************************************************
+     * 
+     ******************************************************************************************************************/
+    public void registerService( MobyService service ) throws MobyException, NoSuccessException,
+            PendingCurationException {
+
+        String result = ( String ) doCall( "registerService", new Object[] { getRegisterServiceXML( service ) } );
+        String[] registered = checkRegistration( result, service );
+        service.setId( registered[ 0 ] );
+        service.setRDF( registered[ 1 ] );
+        String pathToRDF = service.getPathToRDF();
+        if ( !pathToRDF.equals( "" ) ) {
+            File fileRDF = new File( pathToRDF );
+            try {
+                PrintStream fileout = new PrintStream( new FileOutputStream( fileRDF ) );
+                fileout.println( registered[ 1 ] );
+                fileout.close();
+            }
+            catch ( IOException e ) {
+                StringBuffer buf = new StringBuffer( 100 );
+                buf.append( "Failed to save RDF in '" );
+                buf.append( fileRDF.getAbsolutePath() + "'. " );
+                buf.append( e.toString() );
+                try {
+                    File tmpFile = File.createTempFile( service.getName() + "-", ".rdf" );
+                    PrintStream fileout = new PrintStream( new FileOutputStream( tmpFile ) );
+                    fileout.println( registered[ 1 ] );
+                    fileout.close();
+                    buf.append( "\nReturned RDF file was therefore stored in: " );
+                    buf.append( tmpFile.getAbsolutePath() );
+                }
+                catch ( IOException e2 ) {
+                    buf.append( "\nEven saving in a temporary file failed: " );
+                    buf.append( e2.toString() );
+                }
+                throw new MobyException( buf.toString() );
+            }
+        }
+    }
+
+    /*******************************************************************************************************************
+     * 
+     ******************************************************************************************************************/
+    public void unregisterService( MobyService service ) throws MobyException, NoSuccessException,
+            PendingCurationException {
+        String result = ( String ) doCall( "deregisterService", new Object[] { "<deregisterService>" + "<authURI>"
+                + service.getAuthority() + "</authURI>" + "<serviceName>" + service.getName() + "</serviceName>"
+                + "</deregisterService>" } );
+        checkRegistration( result, service );
+    }
+
+    /*******************************************************************************************************************
+     * 
+     ******************************************************************************************************************/
+    public MobyService[] findService( String serviceType ) throws MobyException {
+        if ( serviceType == null ) return new MobyService[] {};
+        MobyService pattern = new MobyService( "dummy" );
+        pattern.setCategory( "" );
+        pattern.setType( serviceType );
+        return findService( pattern, null );
+    }
+
+    /*******************************************************************************************************************
+     * 
+     ******************************************************************************************************************/
+    public MobyService[] findService( String[] keywords ) throws MobyException {
+        if ( keywords == null ) return new MobyService[] {};
+        return findService( null, keywords );
+    }
+
+    /*******************************************************************************************************************
+     * 
+     ******************************************************************************************************************/
+    public MobyService[] findService( MobyService pattern ) throws MobyException {
+        if ( pattern == null ) return new MobyService[] {};
+        return findService( pattern, null );
+    }
+
+    /*******************************************************************************************************************
+     * 
+     ******************************************************************************************************************/
+    public MobyService[] findService( MobyService pattern, String[] keywords ) throws MobyException {
+        return findService( pattern, keywords, true, true );
+    }
+
+    /*******************************************************************************************************************
+     * All 'findService' methods end up here.
+     ******************************************************************************************************************/
+    public MobyService[] findService( MobyService pattern, String[] keywords, boolean includeChildrenServiceTypes,
+            boolean includeParentDataTypes ) throws MobyException {
+        if ( pattern == null ) {
+            pattern = new MobyService( "dummy" );
+            pattern.setCategory( "" );
+        }
+
+        String result = getServicesAsXML( pattern, keywords, includeChildrenServiceTypes, includeParentDataTypes );
+        MobyService[] services = extractServices( result );
+        return services;
+    }
+
+    // ...actually all 'findService' methods end up here
+    protected String getServicesAsXML( MobyService pattern, String[] keywords, boolean includeChildrenServiceTypes,
+            boolean includeParentDataTypes ) throws MobyException {
+        String[] query = new String[] { "<findService>"
+                + buildQueryObject( pattern, keywords, includeParentDataTypes, includeChildrenServiceTypes, false )
+                + "</findService>" };
+        return ( String ) doCall( "findService", query );
+    }
+
+    /*******************************************************************************************************************
+     * 
+     ******************************************************************************************************************/
+    public String call( String methodName, String inputXML ) throws MobyException {
+        Object result;
+        if ( inputXML == null || inputXML.equals( "" ) )
+            result = doCall( methodName, new Object[] {} );
+        else
+            result = doCall( methodName, new Object[] { inputXML } );
+        return ( String ) result;
+    }
+
+    /*******************************************************************************************************************
+     * 
+     ******************************************************************************************************************/
+    protected static String resultToString( Object result ) throws MobyException {
+        if ( result == null ) throw new MobyException( "Returned result is null." );
+        if ( result instanceof String ) return ( String ) result;
+        if ( result instanceof String[] ) {
+            String[] tmp = ( String[] ) result;
+            StringBuffer buf = new StringBuffer();
+            for ( int i = 0; i < tmp.length; i++ )
+                buf.append( tmp[ i ] );
+            return new String( buf );
+        }
+        if ( result instanceof byte[] ) return new String( ( byte[] ) result );
+
+        throw new MobyException( "Unknown type of result: " + result.getClass().getName() );
+    }
+
+    /*******************************************************************************************************************
+     * 
+     ******************************************************************************************************************/
+    public boolean setDebug( boolean enabled ) {
+        boolean oldMode = debug;
+        debug = enabled;
+        return oldMode;
+    }
+
+    /*******************************************************************************************************************
+     * Parses and imports the following XML.
+     * 
+     * <pre>
+     * &lt;Relationships&gt;
+     *   &lt;Relationship relationshipType='urn:lsid:biomoby.org:servicerelation:isa'&gt;
+     *     &lt;serviceType&gt;urn:lsid:biomoby.org:servicetype:analysis&lt;/serviceType&gt;
+     *     &lt;serviceType&gt;urn:lsid:biomoby.org:servicetype:service&lt;/serviceType&gt;
+     *   &lt;/Relationship&gt;
+     * &lt;/Relationships&gt;
+     * </pre>
+     ******************************************************************************************************************/
+    public String[] getServiceTypeRelationships( String serviceTypeName, boolean expand ) throws MobyException {
+        String result = getServiceTypeRelationshipsAsXML( serviceTypeName, expand );
+        return createServiceTypeRelationshipsFromXML( result );
+    }
+
+    //
+    protected String getServiceTypeRelationshipsAsXML( String serviceTypeName, boolean expand ) throws MobyException {
+        return ( String ) doCall( "Relationships", new Object[] { "<Relationship>" + "<serviceType>" + serviceTypeName
+                + "</serviceType>" + "<relationshipType>" + Central.ISA + "</relationshipType>"
+                + "<expandRelationship>" + ( expand ? "1" : "0" ) + "</expandRelationship>" + "</Relationship>" } );
+    }
+
+    //
+    protected String[] createServiceTypeRelationshipsFromXML( String result ) throws MobyException {
+
+        // parse returned XML
+        Vector< String > v = new Vector< String >();
+        Document document = loadDocument( new ByteArrayInputStream( result.getBytes() ) );
+        NodeList list = document.getElementsByTagName( "Relationship" );
+        for ( int i = 0; i < list.getLength(); i++ ) {
+            Element elem = ( Element ) list.item( i );
+            NodeList children = elem.getChildNodes();
+            for ( int j = 0; j < children.getLength(); j++ ) {
+                if ( children.item( j ).getNodeName().equals( "serviceType" ) ) {
+                    v.addElement( getFirstValue( children.item( j ) ) );
+                }
+            }
+        }
+        String[] results = new String[ v.size() ];
+        v.copyInto( results );
+        return results;
+    }
+
+    /*******************************************************************************************************************
+     * Parses and imports the following XML.
+     * 
+     * <pre>
+     * lt;Relationships&gt;
+     *  &lt;Relationship relationshipType='urn:lsid:biomoby.org:objectrelation:isa'&gt;
+     *    &lt;objectType&gt;urn:lsid:biomoby.org:objectclass:virtualsequence&lt;/objectType&gt;
+     *    &lt;objectType&gt;urn:lsid:biomoby.org:objectclass:object&lt;/objectType&gt;
+     *  &lt;/Relationship&gt;
+     *  &lt;Relationship relationshipType='urn:lsid:biomoby.org:objectrelation:hasa'&gt;
+     *    &lt;objectType&gt;urn:lsid:biomoby.org:objectclass:string&lt;/objectType&gt;
+     *    &lt;objectType&gt;urn:lsid:biomoby.org:objectclass:integer&lt;/objectType&gt;
+     *  &lt;/Relationship&gt;
+     * lt;/Relationships&gt;
+     * </pre>
+     * 
+     * Added at Sun Feb 19 19:32:31 PHT 2006: it recognizes also an attributes 'lsid' and 'articleName' in
+     * &lt;objectType&gt; element.
+     ******************************************************************************************************************/
+    public Map getDataTypeRelationships( String dataTypeName ) throws MobyException {
+
+        String cacheId = "getDataTypeRelationships_" + dataTypeName;
+        Map cachedResults = ( Map ) getContents( cacheId );
+        if ( cachedResults != null ) return cachedResults;
+
+        String result = ( String ) doCall( "Relationships", new Object[] { "<Relationships>" + "<objectType>"
+                + dataTypeName + "</objectType>" + "<relationshipType>" + Central.ISA + "</relationshipType>"
+                + "<relationshipType>" + Central.HASA + "</relationshipType>" + "<relationshipType>" + Central.HAS
+                + "</relationshipType>" + "<expandRelationship>1</expandRelationship>" + "</Relationships>" } );
+
+        // parse returned XML
+        Map results = new HashMap();
+        Document document = loadDocument( new ByteArrayInputStream( result.getBytes() ) );
+        NodeList list = document.getElementsByTagName( "Relationship" );
+
+        for ( int i = 0; i < list.getLength(); i++ ) {
+            Element elem = ( Element ) list.item( i );
+            String relType = elem.getAttribute( "relationshipType" );
+            NodeList children = elem.getChildNodes();
+            Vector< String > v = new Vector< String >();
+            for ( int j = 0; j < children.getLength(); j++ ) {
+                if ( children.item( j ).getNodeName().equals( "objectType" ) ) {
+                    v.addElement( getFirstValue( children.item( j ) ) );
+                }
+            }
+            String[] names = new String[ v.size() ];
+            v.copyInto( names );
+            results.put( relType, names );
+        }
+
+        setContents( cacheId, results );
+        return results;
+    }
+
+    /*******************************************************************************************************************
+     * Parses and imports the following XML.
+     * 
+     * <pre>
+     * lt;Relationships&gt;
+     *  &lt;Relationship relationshipType='urn:lsid:biomoby.org:objectrelation:isa'&gt;
+     *    &lt;objectType&gt;urn:lsid:biomoby.org:objectclass:virtualsequence&lt;/objectType&gt;
+     *    &lt;objectType&gt;urn:lsid:biomoby.org:objectclass:object&lt;/objectType&gt;
+     *  &lt;/Relationship&gt;
+     * lt;/Relationships&gt;
+     * </pre>
+     ******************************************************************************************************************/
+    public String[] getDataTypeRelationships( String dataTypeName, String relationshipType ) throws MobyException {
+
+        String cacheId = "getDataTypeRelationships_" + dataTypeName + ":" + relationshipType;
+        String[] cachedResults = ( String[] ) getContents( cacheId );
+        if ( cachedResults != null ) return cachedResults;
+
+        String result = ( String ) doCall( "Relationships", new Object[] { "<Relationships>" + "<objectType>"
+                + dataTypeName + "</objectType>" + "<relationshipType>" + relationshipType + "</relationshipType>"
+                + "<expandRelationship>1</expandRelationship>" + "</Relationships>" } );
+
+        // parse returned XML
+        Vector< String > v = new Vector< String >();
+        Document document = loadDocument( new ByteArrayInputStream( result.getBytes() ) );
+        NodeList list = document.getElementsByTagName( "Relationship" );
+
+        // it should always be just one element in this list
+        for ( int i = 0; i < list.getLength(); i++ ) {
+            Element elem = ( Element ) list.item( i );
+            NodeList children = elem.getChildNodes();
+            for ( int j = 0; j < children.getLength(); j++ ) {
+                if ( children.item( j ).getNodeName().equals( "objectType" ) ) {
+                    v.addElement( getFirstValue( children.item( j ) ) );
+                }
+            }
+        }
+        String[] results = new String[ v.size() ];
+        v.copyInto( results );
+
+        setContents( cacheId, results );
+        return results;
+    }
+
+    // /**************************************************************************
+    // *
+    // *************************************************************************/
+    // public MobyRelationship[] getRelationships (String dataTypeName)
+    // throws MobyException {
+    // return null;
+    // }
+
+    /*******************************************************************************************************************
+     * 
+     ******************************************************************************************************************/
+    public String getRegistryEndpoint() {
+        return endpoint.toString();
+    }
+
+    /*******************************************************************************************************************
+     * 
+     ******************************************************************************************************************/
+    public String getRegistryNamespace() {
+        return uri;
+    }
+
+    /*******************************************************************************************************************
+     * Parses and imports the following XML.
+     * 
+     * <pre>
+     * &lt;resourceURLs&gt;
+     *   &lt;Resource name=&quot;Service&quot;         url=&quot;...&quot; /&gt;
+     *   &lt;Resource name=&quot;Object&quot;          url=&quot;...&quot; /&gt;
+     *   &lt;Resource name=&quot;Namespace&quot;       url=&quot;...&quot; /&gt;
+     *   &lt;Resource name=&quot;ServiceInstance&quot; url=&quot;...&quot; /&gt;
+     *   &lt;Resource name=&quot;Full&quot;            url=&quot;...&quot; /&gt;
+     * &lt;/resourceURLs&gt;
+     * </pre>
+     ******************************************************************************************************************/
+    public MobyResourceRef[] getResourceRefs() throws MobyException {
+
+        String cacheId = "retrieveResourceURLs";
+        MobyResourceRef[] cachedResults = ( MobyResourceRef[] ) getContents( cacheId );
+        if ( cachedResults != null ) return cachedResults;
+
+        String result = ( String ) doCall( "retrieveResourceURLs", new Object[] {} );
+
+        // parse returned XML
+        Vector< MobyResourceRef > v = new Vector< MobyResourceRef >();
+        Document document = loadDocument( new ByteArrayInputStream( result.getBytes() ) );
+        NodeList list = document.getElementsByTagName( "Resource" );
+        for ( int i = 0; i < list.getLength(); i++ ) {
+            Element elem = ( Element ) list.item( i );
+            try {
+                v.addElement( new MobyResourceRef( elem.getAttribute( "name" ), new URL( ( String ) elem
+                        .getAttribute( "url" ) ), elem.getAttribute( "type" ) ) );
+            }
+            catch ( MalformedURLException e2 ) {
+                if ( debug ) System.err.println( "Bad URL: " + elem.getAttribute( "url" ) );
+            }
+        }
+
+        MobyResourceRef[] results = new MobyResourceRef[ v.size() ];
+        v.copyInto( results );
+
+        // Add this data to the cache in case we get called again
+        setContents( cacheId, results );
+
+        return results;
+    }
+
+    /*******************************************************************************************************************
+     * 
+     ******************************************************************************************************************/
+    public InputStream getResource( String resourceName ) throws MobyException {
+
+        MobyResourceRef[] resourceRefs = getResourceRefs();
+        for ( int i = 0; i < resourceRefs.length; i++ ) {
+            if ( resourceName.equalsIgnoreCase( resourceRefs[ i ].getResourceName() ) ) {
+                return Utils.getInputStream( resourceRefs[ i ].getResourceLocation() );
+            }
+        }
+        throw new MobyException( "No resource found for '" + resourceName + "'." );
+    }
+
+    /*******************************************************************************************************************
+     * Return a case-insensitive comparator of Strings. It is used to create various TreeMaps where keys are strings.
+     ******************************************************************************************************************/
+    protected static Comparator getStringComparator() {
+        return new Comparator() {
+            public int compare( Object o1, Object o2 ) {
+                return ( ( String ) o1 ).compareToIgnoreCase( ( String ) o2 );
+            }
+        };
+    }
+
+    // cache URL/URI so we only check once
+    private static String CHECKED_URL = null;
+    private static String CHECKED_URI = null;
+
+    /**
+     * Using this method to get a Central object will ensure that other parts of the org.biomoby.shared class hierarchy
+     * that implicitly check the registry will use the same cache. Otherwise, methods such as
+     * MobyNamespace.getNamespace() must be passed a Central object parameter as well.
+     * 
+     * @return a CentralImpl using the default Central URI, and currently a class implementing a caching mechanism
+     */
+    public static CentralImpl getDefaultCentral() throws MobyException {
+        return getDefaultCentral( null );
+    }
+
+    public static CentralImpl getDefaultCentral( Registry reg ) throws MobyException {
+        if ( reg == null && defaultCentrals.containsKey( "" ) ) {
+            return defaultCentrals.get( "" );
+        }
+        else if ( reg != null && defaultCentrals.containsKey( reg.getEndpoint() ) ) {
+            return defaultCentrals.get( reg.getEndpoint() );
+        }
+
+        String className = DEFAULT_CENTRAL_IMPL_CLASSNAME;
+        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
+        URL resURL = classLoader.getResource( "META-INF/" + CENTRAL_IMPL_RESOURCE_NAME );
+        if ( resURL != null ) {
+            System.err.println( "Loading " + resURL );
+            try {
+                LineNumberReader reader = new LineNumberReader( new InputStreamReader( resURL.openStream() ) );
+                for ( String line = reader.readLine(); line != null; line = reader.readLine() ) {
+                    if ( !line.trim().startsWith( "#" ) ) {
+                        className = line.trim();
+                        break;
+                    }
+                }
+            }
+            catch ( Exception e ) {
+                logger.log( Level.WARNING, "Error reading " + resURL, e );
+            }
+        }
+        try {
+            System.err.println( "Central class is  " + className );
+            Class clazz = Class.forName( className );
+            if ( reg == null ) { // should use default nullary c-tor
+                defaultCentrals.put( "", ( CentralImpl ) clazz.newInstance() );
+            }
+            else { // should have (String endpoint, String namespace) c-tor
+                for ( Constructor ctor : clazz.getDeclaredConstructors() ) {
+                    Class[] params = ctor.getParameterTypes();
+                    if ( params.length == 2 && params[ 0 ].getName().equals( "java.lang.String" )
+                            && params[ 1 ].getName().equals( "java.lang.String" ) ) {
+                        defaultCentrals.put( reg.getEndpoint(), ( CentralImpl ) ctor.newInstance( reg.getEndpoint(),
+                                reg.getNamespace() ) );
+                        break;
+                    }
+                }
+                if ( !defaultCentrals.containsKey( reg.getEndpoint() ) ) {
+                    logger.log( Level.WARNING, "Could not find required (String endpoint, String namespace)"
+                            + "constructor for class " + className );
+                }
+            }
+        }
+        catch ( Exception e ) {
+            logger.log( Level.WARNING, "Could not load class " + className, e );
+            if ( reg == null ) {
+                defaultCentrals.put( "", new CentralImpl() ); // fallback to this class, no caching, etc.
+            }
+            else {
+                defaultCentrals.put( reg.getEndpoint(), new CentralImpl( reg.getEndpoint(), reg.getNamespace() ) );
+            }
+        }
+
+        return defaultCentrals.get( reg == null ? "" : reg.getEndpoint() );
+    }
+
+    /**
+     * 
+     * @return a String representing the Default mobycentral endpoint. If the system property 'moby.check.default'
+     *         exists and is set to true, then the URL http://biomoby.org/mobycentral is queried and the default central
+     *         endpoint is returned, otherwise DEFAULT_ENDPOINT is returned.
+     */
+    public static String getDefaultURL() {
+        boolean check = false;
+        try {
+            check = Boolean.getBoolean( "moby.check.default" );
+        }
+        catch ( Exception e ) {
+
+        }
+
+        if ( check ) {
+            // return the last checked url if we have done this before
+            if ( CHECKED_URL != null && CHECKED_URL.trim() != "" ) {
+                return CHECKED_URL;
+            }
+
+            // create a HttpClient object
+            HttpClient client = new HttpClient();
+            // set up the Head method
+            HeadMethod method = new HeadMethod( "http://biomoby.org/mobycentral" );
+            // do not follow redirects or we will get a 411 error
+            method.setFollowRedirects( false );
+            // retry 3 times
+            method.getParams().setParameter( HttpMethodParams.RETRY_HANDLER,
+                    new DefaultHttpMethodRetryHandler( 3, false ) );
+            // set the user agent ... should probably make this something more reasonable
+            method.getParams().setParameter( HttpMethodParams.USER_AGENT, "jMoby/1.0" );
+            try {
+                // Execute the method.
+                int statusCode = client.executeMethod( method );
+
+                if ( statusCode != HttpStatus.SC_MOVED_PERMANENTLY ) {
+                    System.err.println( "Method failed: " + method.getStatusLine() );
+                }
+                else {
+                    try {
+                        String location = method.getResponseHeader( "location" ).getValue();
+                        CHECKED_URL = location;
+                        try {
+                            CHECKED_URI = "http://" + ( new URL( CHECKED_URL ).getAuthority() ) + "/MOBY/Central";
+                        }
+                        catch ( MalformedURLException murle ) {
+                            CHECKED_URI = DEFAULT_NAMESPACE;
+                        }
+                        return CHECKED_URL;
+                    }
+                    catch ( NullPointerException npe ) {
+                        return DEFAULT_ENDPOINT;
+                    }
+                }
+            }
+            catch ( HttpException e ) {
+                System.err.println( "Fatal protocol violation: " + e.getMessage() );
+                e.printStackTrace();
+            }
+            catch ( IOException e ) {
+                System.err.println( "Fatal transport error: " + e.getMessage() );
+                e.printStackTrace();
+            }
+            finally {
+                // Release the connection.
+                method.releaseConnection();
+            }
+
+        }
+        else {
+            return DEFAULT_ENDPOINT;
+        }
+        return DEFAULT_ENDPOINT;
+    }
+
+    /**
+     * 
+     * @return a String representing the default mobycentral uri. If the system property 'moby.check.default' exists and
+     *         is set to true, then the URL http://biomoby.org/mobycentral is queried and the default central namespace
+     *         is returned, otherwise DEFAULT_NAMESPACE is returned.
+     */
+    public static String getDefaultURI() {
+        boolean check = false;
+        try {
+            check = Boolean.getBoolean( "moby.check.default" );
+        }
+        catch ( Exception e ) {
+
+        }
+        if ( check ) {
+            if ( CHECKED_URI != null && CHECKED_URI.trim() != "" ) {
+                return CHECKED_URI;
+            }
+            // need to check ...
+            getDefaultURL();
+            return CHECKED_URI;
+        }
+        else {
+            return DEFAULT_NAMESPACE;
+        }
+    }
+
+    /*******************************************************************************************************************
+     * Convert non-suitable characters in a XML string into their entity references.
+     * <p>
+     * 
+     * <em>Adapted from jDom.</em>
+     * 
+     * @param str
+     *            input to be converted
+     * @return If there were any non-suitable characters, return a new string with those characters escaped, otherwise
+     *         return the unmodified input string
+     * 
+     ******************************************************************************************************************/
+    public String escapeXML( String str ) {
+        StringBuffer buffer = null;
+        char ch;
+        String entity;
+        for ( int i = 0; i < str.length(); i++ ) {
+            ch = str.charAt( i );
+            switch ( ch ) {
+                case '<':
+                    entity = "&lt;";
+                    break;
+                case '>':
+                    entity = "&gt;";
+                    break;
+                case '&':
+                    entity = "&amp;";
+                    break;
+                default:
+                    entity = null;
+                    break;
+            }
+            if ( buffer == null ) {
+                if ( entity != null ) {
+                    // An entity occurred, so we'll have to use StringBuffer
+                    // (allocate room for it plus a few more entities).
+                    buffer = new StringBuffer( str.length() + 20 );
+                    // Copy previous skipped characters and fall through
+                    // to pickup current character
+                    buffer.append( str.substring( 0, i ) );
+                    buffer.append( entity );
+                }
+            }
+            else {
+                if ( entity == null ) {
+                    buffer.append( ch );
+                }
+                else {
+                    buffer.append( entity );
+                }
+            }
+        }
+
+        // If there were any entities, return the escaped characters
+        // that we put in the StringBuffer. Otherwise, just return
+        // the unmodified input string.
+        return ( buffer == null ) ? str : buffer.toString();
+    }
+
+    /*******************************************************************************************************************
+     * Format an exception.
+     ******************************************************************************************************************/
+    public static String formatFault( AxisFault e, String endpoint, QName method ) {
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        formatFault( e, new PrintStream( baos ), endpoint, method );
+        return baos.toString();
+    }
+
+    /*******************************************************************************************************************
+     * Format an exception.
+     ******************************************************************************************************************/
+    public static void formatFault( AxisFault e, PrintStream out, String endpoint, QName method ) {
+
+        out.println( "===ERROR===" );
+        out.println( "Fault details:" );
+        // for some obvious errors I do not print all details (with a lenghty trace stack)
+        String faultString = e.getFaultString();
+        if ( ( !faultString.startsWith( "java.net.ConnectException" ) )
+                && ( faultString.indexOf( "Could not find class for the service named:" ) == -1 ) ) {
+            org.w3c.dom.Element[] details = e.getFaultDetails();
+            for ( int i = 0; i < details.length; i++ ) {
+                String s = details[ i ].toString().replaceAll( "&lt;", "<" );
+                s = s.replaceAll( "&gt;", ">" );
+                out.println( s );
+            }
+        }
+        out.println( "Fault string: " + faultString );
+        out.println( "Fault code:   " + e.getFaultCode() );
+        out.println( "Fault actor:  " + e.getFaultActor() );
+        if ( endpoint != null || method != null ) out.println( "When calling:" );
+        if ( endpoint != null ) out.println( "\t" + endpoint );
+        if ( method != null ) out.println( "\t" + method );
+        out.println( "===========" );
+    }
+
+}

===================================================================
RCS file: /home/repository/moby/moby-live/Java/src/main/org/biomoby/client/BaseClient.java,v
retrieving revision 1.12
retrieving revision 1.13
diff -u -r1.12 -r1.13
--- /home/repository/moby/moby-live/Java/src/main/org/biomoby/client/BaseClient.java	2007/05/29 03:51:46	1.12
+++ /home/repository/moby/moby-live/Java/src/main/org/biomoby/client/BaseClient.java	2008/11/26 08:53:43	1.13
@@ -1,481 +1,429 @@
-// BaseClient.java
-//
-//    Created: August 2005
-//
-// This file is a component of the BioMoby project.
-// Copyright Martin Senger (martin.senger at gmail.com).
-//
-
-package org.biomoby.client;
-
-import org.biomoby.shared.MobyException;
-import org.biomoby.shared.MobyService;
-import org.biomoby.shared.parser.JDOMUtils;
-import org.biomoby.shared.Central;
-import org.biomoby.client.CentralImpl;
-import org.biomoby.shared.parser.MobyPackage;
-import org.biomoby.shared.parser.MobyJob;
-import org.biomoby.shared.parser.ServiceException;
-import org.biomoby.shared.parser.MobyTags;
-
-import org.tulsoft.tools.soap.axis.AxisCall;
-import org.tulsoft.shared.GException;
-
-import org.apache.commons.lang.StringUtils;
-
-import org.jdom.input.SAXBuilder;
-import org.jdom.Document;
-import org.jdom.Element;
-import org.jdom.Namespace;
-
-import java.net.URL;
-import java.net.MalformedURLException;
-import java.io.StringReader;
-
-/**
- * This is a base class for Biomoby clients. It takes care about
- * converting user input into Biomoby XML, sending it in a SOAP
- * message to a Biomoby service, waiting for the response and parsing
- * it from Biomoby XML. It also divides requests and responses into
- * so-called <em>jobs</em> - each of them corresponds to a Biomoby
- * query (a Biomoby single network request may contain more
- * queries/jobs). <p>
- *
- * Any client can override various methods - but the ones she/he
- * <em>must</em> override in a subclass are those telling what service
- * to call ({@link #getServiceLocator}), what data to put in a request
- * ({@link #fillRequest(MobyJob,MobyPackage) fillRequest}), and what
- * to do with a response ({@link #useResponse(MobyJob,MobyPackage)
- * useResponse}. <p>
- *
- * @author <A HREF="mailto:martin.senger at gmail.com">Martin Senger</A>
- * @version $Id$
- */
-
-abstract public class BaseClient {
-
-    private static org.apache.commons.logging.Log log =
-       org.apache.commons.logging.LogFactory.getLog (BaseClient.class);
-
-    /**************************************************************************
-     *
-     *************************************************************************/
-    static protected boolean notEmpty (String value) {
-	return StringUtils.isNotBlank (value);
-    }
-    static protected boolean isEmpty (String value) {
-	return StringUtils.isBlank (value);
-    }
-
-    /**************************************************************************
-     * The main method that packs input data, invokes a BioMoby
-     * service and uses its response. Use this method if the input
-     * data should have just one job (which is a usual case) -
-     * otherwise use method {@link #process(int)}. <p>
-     *
-     * @throws MobyException if (a) a sub-class throws it during the
-     * filling data or using response, or (b) a Biomoby service
-     * invocation fails
-     *************************************************************************/
-    public void process()
-	throws MobyException {
-	process (1);
-    }
-
-    /**************************************************************************
-     * The main method that packs input data, invokes a BioMoby
-     * service and uses its response. The input data may consist from
-     * more than one job (query) - the 'jobCount' is a suggestion how
-     * many jobs will be included, but this can be changed by the
-     * implementing sub-class. <p>
-     *
-     * Usually a client developer does not need to overwrite this
-     * method. She or he makes the real input data filling in the
-     * {@link #fillRequest} method, and uses the response in the
-     * {@link #useResponse} method. <p>
-     *
-     * @throws MobyException if (a) a sub-class throws it during the
-     * filling data or using response, or (b) a Biomoby service
-     * invocation fails
-     *************************************************************************/
-    public void process (int jobCount)
-	throws MobyException {
-
-	String xmlResponse = null;
-
-	// input: raw-level processing
-	String xmlInput = fillRequest();
-	if (xmlInput != null) {
-	    if ( (xmlInput = interceptRequest (xmlInput)) == null )
-		return;
-	}
-
-	// input: usual processing (i.e. collect XML in iterations)
-	else {
-	    MobyPackage mobyInput = new MobyPackage();
-	    if (! fillRequest (mobyInput, jobCount)) return;
-	    xmlInput = mobyInput.toXML();
-	    if ( (xmlInput = interceptRequest (xmlInput)) == null )
-		return;
-	}
-
-	// calling service
-	xmlResponse = callRemoteService (xmlInput);
-
-	// output: raw-level processing
-	if (! useResponse (xmlResponse)) return;
-
-	// output: usual processing (again by iterations)
-	MobyPackage mobyResponse = MobyPackage.createFromXML (xmlResponse);
-	useResponse (mobyResponse);
-    }
-
-    public String interceptRequest (String xmlInput)
-	throws MobyException {
-	return xmlInput;
-    }
-
-    /**************************************************************************
-     * Create raw XML input. Override this method if you have already
-     * an input XML, or you want to create it yourself. <p>
-     *
-     * @return a full XML input for a Biomoby service (in this case no
-     * other <tt>fillRequest</tt> methods will called); return null if
-     * no XML was created and a usual process to gather it will be used
-     *
-     * @throws MobyException if an XML cannot be created
-     *************************************************************************/
-    public String fillRequest()
-	throws MobyException {
-	return null;
-    }
-
-    /**************************************************************************
-     *
-     *************************************************************************/
-    protected String filterMobyResponseType (Object result)
-	throws MobyException {
-	if (result instanceof String)
-	    return (String)result;
-	else if (result instanceof byte[])
-	    return new String ((byte[])result);
-	else
-	    throw new MobyException
-		("The Biomoby data should be sent/received either as type String or base64/byte[]. " +
-		 "But they are of type '" + result.getClass().getName() + "'.");
-    }
-
-    /**************************************************************************
-     *
-     *************************************************************************/
-    protected String callBiomobyService (MobyServiceLocator locator,
-					 String xmlInput)
-	throws MobyException {
-
-
-	MobyService service = locator.getService();
-	String serviceName = service.getName();
-	boolean asBytes = locator.isAsBytes();
-	String serviceEndpoint = service.getURL();
-	int timeout = locator.getSuggestedTimeout();
-
-	try {
-	    URL target = new URL (serviceEndpoint);
- 	    AxisCall call = new AxisCall (target, timeout);
-	    call.getCall().setSOAPActionURI (MobyService.BIOMOBY_SERVICE_URI + "#" + serviceName);
-	    return filterMobyResponseType
-		(call.doCall (MobyService.BIOMOBY_SERVICE_URI,
-			      serviceName,
-			      new Object[] { sendingFilter (xmlInput, asBytes) }));
-	} catch (MalformedURLException e) {
-	    throw new MobyException ("Service endpoint '" + serviceEndpoint +
-				     "' is not a valid URL.");
-  	} catch (GException e) {
- 	    throw new MobyException (e.getMessage(), e);
-  	}
-    }
-
-    //
-    protected Object sendingFilter (String input, boolean asBytes) {
-	if (asBytes) {
-	    log.debug ("Data sent as a byte array");
-	    return input.getBytes();
-	} else {
-	    return input;
-	}
-    }
-
-    /**************************************************************************
-     * Call a SOAP-based BioMoby service. In order to find what
-     * service to call and what are its characteristics (such as its
-     * endpoint) it will call method {@link #getServiceLocator} that
-     * should be implemented by a sub-class. <p>
-     *
-     * Once it has the service locator, this class does one of the
-     * following, in this order: <ul>
-     *
-     *   <li> The locator must contain at least a service name. If it
-     *   does not, an exception is raised.
-     *
-     *   <li> If the locator contains a service endpoint, a call is
-     *   made to this endpoint, using also the service name as a
-     *   method name.
-     *
-     *   <li> If the locator has a registry endpoint, an enquiry to
-     *   the registry is made to find an endpoint of a service
-     *   corresponding with the given service name. Once found, the
-     *   service is called.
-     *
-     *   <li> The same as the previous one but using a default
-     *   registry.
-     *
-     * @param xmlInput data will be sent to the called Biomoby service
-     *
-     * @return service response (still in XML)
-     *
-     * @throws MobyException (a) if service call (or a call to
-     * registry; for example because the registry does not know given
-     * service) fails, or (b) if the used {@link MobyServiceLocator}
-     * does not contain a service name.
-     *
-     *************************************************************************/
-    public String callRemoteService (String xmlInput)
-	throws MobyException {
-
-	// 1) service name is a must
- 	MobyServiceLocator locator = getServiceLocator();
-	MobyService service = locator.getService();
-	if (service == null)
-	    throw new MobyException ("MobyService locator returned an empty service object.\n" +
-				     "I do not know what service to call...");
-	String serviceName = service.getName();
-	if (isEmpty (serviceName) ||
-	    MobyService.DUMMY_NAME.equals (serviceName))
-	    throw new MobyException ("MobyService locator returned an empty service name.\n" +
-				     "I do not know what service to call...");
-
-	// 2) try service endpoint
-	String serviceEndpoint = service.getURL();
-	if (notEmpty (serviceEndpoint))
-	    return callBiomobyService (locator, xmlInput);
-
-	// 3) find service endpoint in a Biomoby registry
-	Central worker = new CentralImpl (locator.getRegistryEndpoint(),
-					  locator.getRegistryNamespace());
-	MobyService[] services = worker.findService (service);
-	if (services == null || services.length == 0)
-	    throw new MobyException ("Service " + service.toShortString() +
-				     " is not known in Biomoby registry: \n" +
-				     "\t" + worker.getRegistryEndpoint() + "\n" +
-				     "\t" + worker.getRegistryNamespace());
-	// ...and call the service
-	serviceEndpoint = services[0].getURL();
-	if (notEmpty (serviceEndpoint)) {
-	    service.setURL (serviceEndpoint);
-	    return callBiomobyService (locator, xmlInput);
-	}
-
-	// what else can I do?
-	throw new MobyException ("Registry has not returned any URL for service " +
-				 service.toShortString());
-    }
-
-    /**************************************************************************
-     * Fill the whole 'mobyInput' - put there any number of jobs
-     * (queries) as you wish (you do not need to follow the 'jobCount'
-     * hint suggesting how many jobs should be put there). <p>
-     *
-     * Usually there is not need to overwrite this method. It serves
-     * as an inter-mediator between the main {@link #process} method
-     * and the individual request fillings (done by a sub-class in
-     * method {@link #fillRequest(MobyJob,MobyPackage)}). <p>
-     *
-     * @return false if you wish to cancel whole request (nothing will
-     * be sent to a Biomoby service); otherwise return true.
-     *
-     * @param mobyInput is an empty shell that you are supposed to
-     * fill with the input data for a Biomoby service execution
-     *
-     * @param jobCount is only a suggestion how many requests/job
-     * should be created (it comes from the {@link #process} method) -
-     * but it does not need to be obeyed
-     *
-     *************************************************************************/
-    public boolean fillRequest (MobyPackage mobyInput, int jobCount)
-	throws MobyException {
-
-	if (jobCount < 0) jobCount = 0;
-	for (int i = 0; i < jobCount; i++) {
-	    MobyJob request = new MobyJob ("job_" + i);
-	    if (! fillRequest (request, mobyInput))
-		break;
-	    mobyInput.addJob (request);
-	}
-	return (mobyInput.size() > 0);
-    }
-
-    /**************************************************************************
-     * A raw-level processing. Use it if you need access to raw XML
-     * coming from a service. <p>
-     *
-     * @param xmlResponse is a raw XML response returned from a
-     * BioMoby service
-     *
-     * @return false if the response should be considered fully
-     * processed (in this case no other 'useResponse' will be called);
-     * true indicates that normal processing of the response will
-     * follow; by default, this class (<tt>BaseClient</tt>) returns true
-     *
-     * @throws MobyException if you are not satisfied with a response
-     * data, or from whatever reasons; it also throws this exception
-     * if the 'mobyResponse' is broken
-     *************************************************************************/
-    public boolean useResponse (String xmlResponse)
-	throws MobyException {
-	return true;
-    }
-
-    /**************************************************************************
-     * A high-level processing. Use it if you need access to all jobs
-     * (queries) - that returned from a service - in the same time.
-     * Otherwise use the processing on the job level (method {@link
-     * #useResponse(MobyJob,MobyPackage)}. <p>
-     *
-     * @param mobyResponse is a full response returned from a BioMoby
-     * service
-     *
-     * @throws MobyException if you are not satisfied with a response
-     * data, or from whatever reasons; it also throws this exception
-     * if the 'mobyResponse' is broken
-     *************************************************************************/
-    public void useResponse (MobyPackage mobyResponse)
-	throws MobyException {
-
-	// iterate over all input jobs
-	for (int i = 0; i < mobyResponse.size(); i++) {
-	    if (! useResponse (mobyResponse.getJob (i),
-			       mobyResponse))
-		return;
-	}
-    }
-
-    /**************************************************************************
-     * Extracts errors from a raw service response. It is iseful when
-     * one does not want to create a whole <tt>MobyPackage</tt> from a
-     * response, but just find whether a response is good or bad. <p>
-     *
-     * @param xmlResponse is a full response returned from a BioMoby
-     * service
-     *
-     * @return a slightly formatted list of exceptions (of severity
-     * <em>error</em>) extracted from the response; or null if there
-     * are no errors there
-     *************************************************************************/
-    public String errorsInResponse (String xmlResponse) {
-	try {
-	    StringBuffer buf = new StringBuffer();
-	    SAXBuilder builder = new SAXBuilder();
-	    Document doc =
-		builder.build (new StringReader (xmlResponse));
-	    Element root = doc.getRootElement();
-	    Element mobyContent = JDOMUtils.getChild (root, MobyTags.MOBYCONTENT);
-	    Element serviceNotes = JDOMUtils.getChild (mobyContent, MobyTags.SERVICENOTES);
-	    ServiceException[] exceptions =
-		ServiceException.extractExceptions (serviceNotes);
-	    for (int i = 0; i < exceptions.length; i++) {
-		if (exceptions[i].getSeverity() != ServiceException.ERROR)
-		    continue;
-		if (buf.length() > 0)
-		    buf.append ("\n");
-		buf.append (exceptions[i].getErrorCodeAsString());
-		buf.append (": ");
-		buf.append (exceptions[i].getMessage());
-	    }
-	    return (buf.length() == 0 ? null : new String (buf));
-	    
-	} catch (Exception e) {
-	    return e.toString();
-	}
-    }
-
-
-    //
-    // Abstract methods
-    //
-
-    /**************************************************************************
-     * Return characteristics of a BioMoby service that will be
-     * called, and that reveal where to find such service. <p>
-     *
-     * @see #callRemoteService
-     *
-     * @return an object defining a location of a BioMoby service
-     * @throws MobyException if service locator cannot be
-     * returned/created (e.g. because there is not enough information
-     * about what service to call)
-     *************************************************************************/
-    abstract public MobyServiceLocator getServiceLocator()
-	throws MobyException;
-
-    /**************************************************************************
-     * Crate data (fill them into 'request') for one Moby job
-     * (query). The request will be sent within given 'inputContext' -
-     * but it is not yet there (because you may wish not to put it
-     * there - see the return value), and it is not the task of this
-     * method to put it there (just fill the 'request'). <p>
-     *
-     * This is a method that should be implemented by a client
-     * developer, and it is the place where the client's business
-     * logic sits. <p>
-     *
-     * @return true if this request should be included into the input
-     * data (package) that will be sent to a biomoby service; or
-     * return false if you do not wish to create any more requests for
-     * this particular package (also this 'request' will not be used)
-     *
-     * @param request is an object that you are supposed to fill with
-     * input data for one service invocation; it already has a name
-     * (so called 'query id' in the BioMoby speak) but you are free to change it
-     *
-     * @param inputContext is an envelope where all requests will be
-     * stored and sent to a Biomoby service; you do not need to do
-     * anything with it here unless you wish; note that all already
-     * created requests are there, but not the one you are just
-     * creating in this method
-     *
-     * @throws MobyException if you need so (from whatever reason in
-     * your business logic); if thrown then nothing will be sent to a
-     * Biomoby service
-     *
-     *************************************************************************/
-    abstract public boolean fillRequest (MobyJob request, MobyPackage inputContext)
-	throws MobyException;
-
-    /**************************************************************************
-     * Process a single job returned from a BioMoby service. <p>
-     *
-     * This is a method that should be implemented by a client
-     * developer, and it is the place where the client's business
-     * logic using the response sits. <p>
-     *
-     * @param response is an object that you are supposed to use
-     *
-     * @param responseContext is an envelope where the full response
-     * (all its jobs) is located; you do not need to do anything with
-     * it here unless you wish (e.g. it gives you knowledge about how
-     * many jobs are in the full response, or it gives you access to
-     * the so-called 'service notes')
-     *
-     * @return false if you do not wish to get any more
-     * responses/jobs; otherwise return true
-     *
-     * @throws MobyException if you are not satisfied with a response
-     * data, or from whatever reasons; it also throws this exception
-     * if the 'response' is broken
-     *
-     *************************************************************************/
-    abstract public boolean useResponse (MobyJob response,
-					 MobyPackage responseContext)
-	throws MobyException;
-
-}
+// BaseClient.java
+//
+//    Created: August 2005
+//
+// This file is a component of the BioMoby project.
+// Copyright Martin Senger (martin.senger at gmail.com).
+//
+
+package org.biomoby.client;
+
+import java.io.StringReader;
+import java.net.MalformedURLException;
+import java.net.URL;
+
+import org.apache.axis.client.Call;
+import org.apache.commons.lang.StringUtils;
+import org.biomoby.shared.Central;
+import org.biomoby.shared.MobyException;
+import org.biomoby.shared.MobyService;
+import org.biomoby.shared.parser.JDOMUtils;
+import org.biomoby.shared.parser.MobyJob;
+import org.biomoby.shared.parser.MobyPackage;
+import org.biomoby.shared.parser.MobyTags;
+import org.biomoby.shared.parser.ServiceException;
+import org.jdom.Document;
+import org.jdom.Element;
+import org.jdom.input.SAXBuilder;
+import org.tulsoft.shared.GException;
+import org.tulsoft.tools.soap.axis.AxisCall;
+
+/**
+ * This is a base class for Biomoby clients. It takes care about converting user input into Biomoby XML, sending it in a
+ * SOAP message to a Biomoby service, waiting for the response and parsing it from Biomoby XML. It also divides requests
+ * and responses into so-called <em>jobs</em> - each of them corresponds to a Biomoby query (a Biomoby single network
+ * request may contain more queries/jobs).
+ * <p>
+ * 
+ * Any client can override various methods - but the ones she/he <em>must</em> override in a subclass are those
+ * telling what service to call ({@link #getServiceLocator}), what data to put in a request ({@link #fillRequest(MobyJob,MobyPackage) fillRequest}),
+ * and what to do with a response ({@link #useResponse(MobyJob,MobyPackage) useResponse}.
+ * <p>
+ * 
+ * @author <A HREF="mailto:martin.senger at gmail.com">Martin Senger</A>
+ * @version $Id$
+ */
+
+abstract public class BaseClient {
+	private static org.apache.commons.logging.Log log = org.apache.commons.logging.LogFactory.getLog( BaseClient.class );
+
+	/*******************************************************************************************************************
+	 * 
+	 ******************************************************************************************************************/
+	static protected boolean notEmpty(String value) {
+		return StringUtils.isNotBlank( value );
+	}
+	static protected boolean isEmpty(String value) {
+		return StringUtils.isBlank( value );
+	}
+
+	/*******************************************************************************************************************
+	 * The main method that packs input data, invokes a BioMoby service and uses its response. Use this method if the
+	 * input data should have just one job (which is a usual case) - otherwise use method {@link #process(int)}.
+	 * <p>
+	 * 
+	 * @throws MobyException if (a) a sub-class throws it during the filling data or using response, or (b) a Biomoby
+	 *             service invocation fails
+	 ******************************************************************************************************************/
+	public void process() throws MobyException {
+		process( 1 );
+	}
+
+	/*******************************************************************************************************************
+	 * The main method that packs input data, invokes a BioMoby service and uses its response. The input data may
+	 * consist from more than one job (query) - the 'jobCount' is a suggestion how many jobs will be included, but this
+	 * can be changed by the implementing sub-class.
+	 * <p>
+	 * 
+	 * Usually a client developer does not need to overwrite this method. She or he makes the real input data filling in
+	 * the {@link #fillRequest} method, and uses the response in the {@link #useResponse} method.
+	 * <p>
+	 * 
+	 * @throws MobyException if (a) a sub-class throws it during the filling data or using response, or (b) a Biomoby
+	 *             service invocation fails
+	 ******************************************************************************************************************/
+	public void process(int jobCount) throws MobyException {
+
+		String xmlResponse = null;
+
+		// input: raw-level processing
+		String xmlInput = fillRequest();
+		if ( xmlInput != null ) {
+			if ( ( xmlInput = interceptRequest( xmlInput ) ) == null )
+				return;
+		}
+
+		// input: usual processing (i.e. collect XML in iterations)
+		else {
+			MobyPackage mobyInput = new MobyPackage();
+			if ( !fillRequest( mobyInput, jobCount ) )
+				return;
+			xmlInput = mobyInput.toXML();
+			if ( ( xmlInput = interceptRequest( xmlInput ) ) == null )
+				return;
+		}
+
+		// calling service
+		xmlResponse = callRemoteService( xmlInput );
+
+		// output: raw-level processing
+		if ( !useResponse( xmlResponse ) )
+			return;
+
+		// output: usual processing (again by iterations)
+		MobyPackage mobyResponse = MobyPackage.createFromXML( xmlResponse );
+		useResponse( mobyResponse );
+	}
+
+	public String interceptRequest(String xmlInput) throws MobyException {
+		return xmlInput;
+	}
+
+	/*******************************************************************************************************************
+	 * Create raw XML input. Override this method if you have already an input XML, or you want to create it yourself.
+	 * <p>
+	 * 
+	 * @return a full XML input for a Biomoby service (in this case no other <tt>fillRequest</tt> methods will
+	 *         called); return null if no XML was created and a usual process to gather it will be used
+	 * 
+	 * @throws MobyException if an XML cannot be created
+	 ******************************************************************************************************************/
+	public String fillRequest() throws MobyException {
+		return null;
+	}
+
+	/*******************************************************************************************************************
+	 * 
+	 ******************************************************************************************************************/
+	protected String filterMobyResponseType(Object result) throws MobyException {
+		if ( result instanceof String )
+			return ( String ) result;
+		else if ( result instanceof byte[] )
+			return new String( ( byte[] ) result );
+		else
+			throw new MobyException(
+					"The Biomoby data should be sent/received either as type String or base64/byte[]. "
+							+ "But they are of type '" + result.getClass().getName() + "'." );
+	}
+
+	/*******************************************************************************************************************
+	 * 
+	 ******************************************************************************************************************/
+	protected String callBiomobyService(MobyServiceLocator locator, String xmlInput) throws MobyException {
+
+		MobyService service = locator.getService();
+		String serviceName = service.getName();
+		boolean asBytes = locator.isAsBytes();
+		String serviceEndpoint = service.getURL();
+		int timeout = locator.getSuggestedTimeout();
+
+		try {
+			URL target = new URL( serviceEndpoint );
+			AxisCall call = new AxisCall( target, timeout );
+			// to allow service calls which need authentication
+			setAuthentication( call.getCall() );
+			call.getCall().setSOAPActionURI( MobyService.BIOMOBY_SERVICE_URI + "#" + serviceName );
+
+			return filterMobyResponseType( call.doCall( MobyService.BIOMOBY_SERVICE_URI, serviceName,
+														new Object[]{sendingFilter( xmlInput, asBytes )} ) );
+		}
+		catch ( MalformedURLException e ) {
+			throw new MobyException( "Service endpoint '" + serviceEndpoint + "' is not a valid URL." );
+		}
+		catch ( GException e ) {
+			throw new MobyException( e.getMessage(), e );
+		}
+	}
+
+	/**
+	 * Sets the authentication for the call. The default implementation does not do anything !
+	 * 
+	 * @param call
+	 */
+	protected void setAuthentication(Call call) {
+		return;
+	}
+
+	//
+	protected Object sendingFilter(String input, boolean asBytes) {
+		if ( asBytes ) {
+			log.debug( "Data sent as a byte array" );
+			return input.getBytes();
+		}
+		else {
+			return input;
+		}
+	}
+
+	/*******************************************************************************************************************
+	 * Call a SOAP-based BioMoby service. In order to find what service to call and what are its characteristics (such
+	 * as its endpoint) it will call method {@link #getServiceLocator} that should be implemented by a sub-class.
+	 * <p>
+	 * 
+	 * Once it has the service locator, this class does one of the following, in this order:
+	 * <ul>
+	 * 
+	 * <li> The locator must contain at least a service name. If it does not, an exception is raised.
+	 * 
+	 * <li> If the locator contains a service endpoint, a call is made to this endpoint, using also the service name as
+	 * a method name.
+	 * 
+	 * <li> If the locator has a registry endpoint, an enquiry to the registry is made to find an endpoint of a service
+	 * corresponding with the given service name. Once found, the service is called.
+	 * 
+	 * <li> The same as the previous one but using a default registry.
+	 * 
+	 * @param xmlInput data will be sent to the called Biomoby service
+	 * 
+	 * @return service response (still in XML)
+	 * 
+	 * @throws MobyException (a) if service call (or a call to registry; for example because the registry does not know
+	 *             given service) fails, or (b) if the used {@link MobyServiceLocator} does not contain a service name.
+	 * 
+	 ******************************************************************************************************************/
+	public String callRemoteService(String xmlInput) throws MobyException {
+
+		// 1) service name is a must
+		MobyServiceLocator locator = getServiceLocator();
+		MobyService service = locator.getService();
+		if ( service == null )
+			throw new MobyException( "MobyService locator returned an empty service object.\n"
+					+ "I do not know what service to call..." );
+		String serviceName = service.getName();
+		if ( isEmpty( serviceName ) || MobyService.DUMMY_NAME.equals( serviceName ) )
+			throw new MobyException( "MobyService locator returned an empty service name.\n"
+					+ "I do not know what service to call..." );
+
+		// 2) try service endpoint
+		String serviceEndpoint = service.getURL();
+		if ( notEmpty( serviceEndpoint ) )
+			return callBiomobyService( locator, xmlInput );
+
+		// 3) find service endpoint in a Biomoby registry
+		Central worker = new CentralImpl( locator.getRegistryEndpoint(), locator.getRegistryNamespace() );
+		MobyService[] services = worker.findService( service );
+		if ( services == null || services.length == 0 )
+			throw new MobyException( "Service " + service.toShortString() + " is not known in Biomoby registry: \n"
+					+ "\t" + worker.getRegistryEndpoint() + "\n" + "\t" + worker.getRegistryNamespace() );
+		// ...and call the service
+		serviceEndpoint = services[ 0 ].getURL();
+		if ( notEmpty( serviceEndpoint ) ) {
+			service.setURL( serviceEndpoint );
+			return callBiomobyService( locator, xmlInput );
+		}
+
+		// what else can I do?
+		throw new MobyException( "Registry has not returned any URL for service " + service.toShortString() );
+	}
+
+	/*******************************************************************************************************************
+	 * Fill the whole 'mobyInput' - put there any number of jobs (queries) as you wish (you do not need to follow the
+	 * 'jobCount' hint suggesting how many jobs should be put there).
+	 * <p>
+	 * 
+	 * Usually there is not need to overwrite this method. It serves as an inter-mediator between the main
+	 * {@link #process} method and the individual request fillings (done by a sub-class in method
+	 * {@link #fillRequest(MobyJob,MobyPackage)}).
+	 * <p>
+	 * 
+	 * @return false if you wish to cancel whole request (nothing will be sent to a Biomoby service); otherwise return
+	 *         true.
+	 * 
+	 * @param mobyInput is an empty shell that you are supposed to fill with the input data for a Biomoby service
+	 *            execution
+	 * 
+	 * @param jobCount is only a suggestion how many requests/job should be created (it comes from the {@link #process}
+	 *            method) - but it does not need to be obeyed
+	 * 
+	 ******************************************************************************************************************/
+	public boolean fillRequest(MobyPackage mobyInput, int jobCount) throws MobyException {
+
+		if ( jobCount < 0 )
+			jobCount = 0;
+		for ( int i = 0; i < jobCount; i++ ) {
+			MobyJob request = new MobyJob( "job_" + i );
+			if ( !fillRequest( request, mobyInput ) )
+				break;
+			mobyInput.addJob( request );
+		}
+		return ( mobyInput.size() > 0 );
+	}
+
+	/*******************************************************************************************************************
+	 * A raw-level processing. Use it if you need access to raw XML coming from a service.
+	 * <p>
+	 * 
+	 * @param xmlResponse is a raw XML response returned from a BioMoby service
+	 * 
+	 * @return false if the response should be considered fully processed (in this case no other 'useResponse' will be
+	 *         called); true indicates that normal processing of the response will follow; by default, this class (<tt>BaseClient</tt>)
+	 *         returns true
+	 * 
+	 * @throws MobyException if you are not satisfied with a response data, or from whatever reasons; it also throws
+	 *             this exception if the 'mobyResponse' is broken
+	 ******************************************************************************************************************/
+	public boolean useResponse(String xmlResponse) throws MobyException {
+		return true;
+	}
+
+	/*******************************************************************************************************************
+	 * A high-level processing. Use it if you need access to all jobs (queries) - that returned from a service - in the
+	 * same time. Otherwise use the processing on the job level (method {@link #useResponse(MobyJob,MobyPackage)}.
+	 * <p>
+	 * 
+	 * @param mobyResponse is a full response returned from a BioMoby service
+	 * 
+	 * @throws MobyException if you are not satisfied with a response data, or from whatever reasons; it also throws
+	 *             this exception if the 'mobyResponse' is broken
+	 ******************************************************************************************************************/
+	public void useResponse(MobyPackage mobyResponse) throws MobyException {
+
+		// iterate over all input jobs
+		for ( int i = 0; i < mobyResponse.size(); i++ ) {
+			if ( !useResponse( mobyResponse.getJob( i ), mobyResponse ) )
+				return;
+		}
+	}
+
+	/*******************************************************************************************************************
+	 * Extracts errors from a raw service response. It is iseful when one does not want to create a whole
+	 * <tt>MobyPackage</tt> from a response, but just find whether a response is good or bad.
+	 * <p>
+	 * 
+	 * @param xmlResponse is a full response returned from a BioMoby service
+	 * 
+	 * @return a slightly formatted list of exceptions (of severity <em>error</em>) extracted from the response; or
+	 *         null if there are no errors there
+	 ******************************************************************************************************************/
+	public String errorsInResponse(String xmlResponse) {
+		try {
+			StringBuffer buf = new StringBuffer();
+			SAXBuilder builder = new SAXBuilder();
+			Document doc = builder.build( new StringReader( xmlResponse ) );
+			Element root = doc.getRootElement();
+			Element mobyContent = JDOMUtils.getChild( root, MobyTags.MOBYCONTENT );
+			Element serviceNotes = JDOMUtils.getChild( mobyContent, MobyTags.SERVICENOTES );
+			ServiceException[] exceptions = ServiceException.extractExceptions( serviceNotes );
+			for ( int i = 0; i < exceptions.length; i++ ) {
+				if ( exceptions[ i ].getSeverity() != ServiceException.ERROR )
+					continue;
+				if ( buf.length() > 0 )
+					buf.append( "\n" );
+				buf.append( exceptions[ i ].getErrorCodeAsString() );
+				buf.append( ": " );
+				buf.append( exceptions[ i ].getMessage() );
+			}
+			return ( buf.length() == 0 ? null : new String( buf ) );
+
+		}
+		catch ( Exception e ) {
+			return e.toString();
+		}
+	}
+
+	//
+	// Abstract methods
+	//
+
+	/*******************************************************************************************************************
+	 * Return characteristics of a BioMoby service that will be called, and that reveal where to find such service.
+	 * <p>
+	 * 
+	 * @see #callRemoteService
+	 * 
+	 * @return an object defining a location of a BioMoby service
+	 * @throws MobyException if service locator cannot be returned/created (e.g. because there is not enough information
+	 *             about what service to call)
+	 ******************************************************************************************************************/
+	abstract public MobyServiceLocator getServiceLocator() throws MobyException;
+
+	/*******************************************************************************************************************
+	 * Crate data (fill them into 'request') for one Moby job (query). The request will be sent within given
+	 * 'inputContext' - but it is not yet there (because you may wish not to put it there - see the return value), and
+	 * it is not the task of this method to put it there (just fill the 'request').
+	 * <p>
+	 * 
+	 * This is a method that should be implemented by a client developer, and it is the place where the client's
+	 * business logic sits.
+	 * <p>
+	 * 
+	 * @return true if this request should be included into the input data (package) that will be sent to a biomoby
+	 *         service; or return false if you do not wish to create any more requests for this particular package (also
+	 *         this 'request' will not be used)
+	 * 
+	 * @param request is an object that you are supposed to fill with input data for one service invocation; it already
+	 *            has a name (so called 'query id' in the BioMoby speak) but you are free to change it
+	 * 
+	 * @param inputContext is an envelope where all requests will be stored and sent to a Biomoby service; you do not
+	 *            need to do anything with it here unless you wish; note that all already created requests are there,
+	 *            but not the one you are just creating in this method
+	 * 
+	 * @throws MobyException if you need so (from whatever reason in your business logic); if thrown then nothing will
+	 *             be sent to a Biomoby service
+	 * 
+	 ******************************************************************************************************************/
+	abstract public boolean fillRequest(MobyJob request, MobyPackage inputContext) throws MobyException;
+
+	/*******************************************************************************************************************
+	 * Process a single job returned from a BioMoby service.
+	 * <p>
+	 * 
+	 * This is a method that should be implemented by a client developer, and it is the place where the client's
+	 * business logic using the response sits.
+	 * <p>
+	 * 
+	 * @param response is an object that you are supposed to use
+	 * 
+	 * @param responseContext is an envelope where the full response (all its jobs) is located; you do not need to do
+	 *            anything with it here unless you wish (e.g. it gives you knowledge about how many jobs are in the full
+	 *            response, or it gives you access to the so-called 'service notes')
+	 * 
+	 * @return false if you do not wish to get any more responses/jobs; otherwise return true
+	 * 
+	 * @throws MobyException if you are not satisfied with a response data, or from whatever reasons; it also throws
+	 *             this exception if the 'response' is broken
+	 * 
+	 ******************************************************************************************************************/
+	abstract public boolean useResponse(MobyJob response, MobyPackage responseContext) throws MobyException;
+
+}




More information about the MOBY-guts mailing list