[MOBY-guts] biomoby commit

Martin Senger senger at dev.open-bio.org
Wed Dec 3 12:22:21 UTC 2008


senger
Wed Dec  3 07:22:21 EST 2008
Update of /home/repository/moby/moby-live/Java/src/main/org/biomoby/client
In directory dev.open-bio.org:/tmp/cvs-serv12031/S/client

Modified Files:
	CentralImpl.java 
Log Message:
revert to 1.57
moby-live/Java/src/main/org/biomoby/client CentralImpl.java,1.59,1.60
===================================================================
RCS file: /home/repository/moby/moby-live/Java/src/main/org/biomoby/client/CentralImpl.java,v
retrieving revision 1.59
retrieving revision 1.60
diff -u -r1.59 -r1.60
--- /home/repository/moby/moby-live/Java/src/main/org/biomoby/client/CentralImpl.java	2008/12/03 12:09:40	1.59
+++ /home/repository/moby/moby-live/Java/src/main/org/biomoby/client/CentralImpl.java	2008/12/03 12:22:21	1.60
@@ -1,2139 +1,2139 @@
-// 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 < 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 ("===========");
+    }
+
+
+}




More information about the MOBY-guts mailing list