[MOBY-guts] biomoby commit

senger@ebi.ac.uk senger at pub.open-bio.org
Mon Oct 18 14:35:06 UTC 2004


senger
Mon Oct 18 10:35:06 EDT 2004
Update of /home/repository/moby/moby-live/Java/src/main/org/biomoby/client
In directory pub.open-bio.org:/tmp/cvs-serv2405/src/main/org/biomoby/client

Modified Files:
	CentralImpl.java FileCache.java FilterServices.java 
	GraphsServlet.java Graphviz.java ServiceConnections.java 
	ServicesEdge.java ServletFileCache.java SimpleCache.java 
Added Files:
	CentralDigestCachedImpl.java CentralDigestImpl.java 
	DataServiceEdge.java DataTypeConnector.java 
	SimpleFileCache.java Taverna.java 
Log Message:
searching data paths and more, see docs/ChangeLog

moby-live/Java/src/main/org/biomoby/client CentralDigestCachedImpl.java,NONE,1.1 CentralDigestImpl.java,NONE,1.1 DataServiceEdge.java,NONE,1.1 DataTypeConnector.java,NONE,1.1 SimpleFileCache.java,NONE,1.1 Taverna.java,NONE,1.1 CentralImpl.java,1.16,1.17 FileCache.java,1.3,1.4 FilterServices.java,1.1,1.2 GraphsServlet.java,1.6,1.7 Graphviz.java,1.4,1.5 ServiceConnections.java,1.2,1.3 ServicesEdge.java,1.2,1.3 ServletFileCache.java,1.2,1.3 SimpleCache.java,1.1,1.2
===================================================================
RCS file: /home/repository/moby/moby-live/Java/src/main/org/biomoby/client/CentralImpl.java,v
retrieving revision 1.16
retrieving revision 1.17
diff -u -r1.16 -r1.17
--- /home/repository/moby/moby-live/Java/src/main/org/biomoby/client/CentralImpl.java	2004/09/24 19:52:55	1.16
+++ /home/repository/moby/moby-live/Java/src/main/org/biomoby/client/CentralImpl.java	2004/10/18 14:35:06	1.17
@@ -55,14 +55,12 @@
  */
 
 public class CentralImpl
-    implements Central {
+    implements Central, SimpleCache {
 
-    private Hashtable cache;  // To not call MOBY Central everytime the same method is called
     private URL endpoint;
     private String uri;
     private javax.xml.parsers.DocumentBuilder docBuilder;
-    private boolean debug = false;
-    private boolean useCache = true;
+    protected boolean debug = false;
 
     /** Default location (endpoint) of a Moby registry. */
 //     public static final String DEFAULT_ENDPOINT = "http://mobycentral.cbr.nrc.ca/cgi-bin/MOBY-Central.pl";
@@ -429,7 +427,7 @@
      *
      * Throws an exception if the XML document is invalid.
      *************************************************************************/
-    protected MobyService[] extractServices (String xml)
+    public MobyService[] extractServices (String xml)
 	throws MobyException {
 	    
 	Document document = null;
@@ -449,19 +447,19 @@
 	    for (int j = 0; j < children.getLength(); j++) {
 		String nodeName = children.item (j).getNodeName();
 		if (nodeName.equals ("Description")) {
-		    service.setDescription (children.item (j).getFirstChild().getNodeValue());
+		    service.setDescription (getFirstValue (children.item (j)));
 		} else if (nodeName.equals ("Category")) {
-		    service.setCategory (children.item (j).getFirstChild().getNodeValue());
+		    service.setCategory (getFirstValue (children.item (j)));
 		} else if (nodeName.equals ("URL")) {
-		    service.setURL (children.item (j).getFirstChild().getNodeValue());
+		    service.setURL (getFirstValue (children.item (j)));
 		} else if (nodeName.equals ("signatureURL")) {
-		    service.setSignatureURL (children.item (j).getFirstChild().getNodeValue());
+		    service.setSignatureURL (getFirstValue (children.item (j)));
 		} else if (nodeName.equals ("contactEmail")) {
-		    service.setEmailContact (children.item (j).getFirstChild().getNodeValue());
+		    service.setEmailContact (getFirstValue (children.item (j)));
 		} else if (nodeName.equals ("serviceType")) {
-		    service.setType (children.item (j).getFirstChild().getNodeValue());
+		    service.setType (getFirstValue (children.item (j)));
 		} else if (nodeName.equals ("authoritative")) {
-		    String authoritative = children.item (j).getFirstChild().getNodeValue();
+		    String authoritative = getFirstValue (children.item (j));
 		    service.setAuthoritative (authoritative.equals ("1") ? true : false);
 		} else if (nodeName.equals ("Input")) {
 		    // <Input>
@@ -517,6 +515,96 @@
 	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;
+    }
+
+    /**************************************************************************
+     * 
+     * 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 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 requery
+     * 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;
+    }
+
     /**************************************************************************
      * B 
      *  <serviceNames>
@@ -528,10 +616,10 @@
     public Map getServiceNames()
 	throws MobyException {
 
-	// First, check to se if we have the values cached from a previous call 
-	// in this instance
-	if (useCache && cache.containsKey("retrieveServiceNames"))
-	    return (Map) cache.get("retrieveServiceNames");
+	String cacheId = "retrieveServiceNames";
+	Map cachedResults = (Map)getContents (cacheId);
+	if (cachedResults != null)
+	    return cachedResults;
 
 	String result = (String)doCall ("retrieveServiceNames",
 					new Object[] {});
@@ -553,8 +641,8 @@
 	}
 
 	// Add this data to the cache in case we get called again
-	if (useCache)
-	    cache.put("retrieveServiceNames", results);
+	setContents (cacheId, results);
+
 	return results;
     }
 
@@ -569,9 +657,10 @@
     public String[] getProviders()
 	throws MobyException {
 
-	// First, see if we have the values cached from a previous call in this instance
-	if (useCache && cache.containsKey ("retrieveServiceProviders"))
-	    return (String[]) cache.get ("retrieveServiceProviders");
+	String cacheId = "retrieveServiceProviders";
+	String[] cachedResults = (String[])getContents (cacheId);
+	if (cachedResults != null)
+	    return cachedResults;
 
 	String result = (String)doCall ("retrieveServiceProviders",
 					new Object[] {});
@@ -590,8 +679,8 @@
 	    results[i] = ((Element)list.item (i)).getAttribute ("name");
 
 	// Add this data to the cache in case we get called again
-	if (useCache)
-	    cache.put("retrieveServiceProviders", results);
+	setContents (cacheId, results);
+
 	return results;
     }
 
@@ -608,9 +697,11 @@
      *************************************************************************/
     public Map getServiceTypes()
 	throws MobyException {
-	// First, see if we have the values cached from a previous call in this instance
-	if (useCache && cache.containsKey ("retrieveServiceTypes"))
-	    return (Map) cache.get("retrieveServiceTypes");
+
+	String cacheId = "retrieveServiceTypes";
+	Map cachedResults = (Map)getContents (cacheId);
+	if (cachedResults != null)
+	    return cachedResults;
 
 	String result = (String)doCall ("retrieveServiceTypes",
 					new Object[] {});
@@ -629,15 +720,15 @@
 	    for (int j = 0; j < children.getLength(); j++) {
 		if (children.item (j).getNodeName().equals ("Description")) {
 		    results.put (elem.getAttribute ("name"),
-				 children.item (j).getFirstChild().getNodeValue());
+				 getFirstValue (children.item (j)));
 		    break;
 		}
 	    }
 	}
 
 	// Add this data to the cache in case we get called again
-	if(useCache)
-	    cache.put("retrieveServiceTypes", results);
+	setContents (cacheId, results);
+
 	return results;
     }
 
@@ -655,9 +746,10 @@
     public Map getNamespaces()
 	throws MobyException {
 
-	// First, see if we have the values cached from a previous call in this instance
-	if(useCache && cache.containsKey("retrieveNamespaces"))
-	    return (Map) cache.get("retrieveNamespaces");
+	String cacheId = "retrieveNamespaces";
+	Map cachedResults = (Map)getContents (cacheId);
+	if (cachedResults != null)
+	    return cachedResults;
 
 	String result = (String)doCall ("retrieveNamespaces",
 					new Object[] {});
@@ -683,7 +775,9 @@
 	    if (children.item(0).hasChildNodes()) {
 		children.item(0).normalize();
 		results.put (elem.getAttribute ("name"),
-			     children.item(0).getFirstChild().getNodeValue());
+			     getFirstValue (children.item(0)));
+
+
 	    } else {
 		// No description provided
 		results.put (elem.getAttribute ("name"), "");
@@ -691,8 +785,8 @@
 	}
 
 	// Add this data to the cache in case we get called again
-	if (useCache)
-	    cache.put("retrieveNamespaces", results);
+	setContents (cacheId, results);
+
 	return results;
     }
 
@@ -709,9 +803,11 @@
      *************************************************************************/
     public Map getDataTypeNames()
 	throws MobyException {
-	// First, see if we have the values cached from a previous call in this instance
-	if (useCache && cache.containsKey("retrieveObjectNames"))
-	    return (Map) cache.get("retrieveObjectNames");
+
+	String cacheId = "retrieveObjectNames";
+	Map cachedResults = (Map)getContents (cacheId);
+	if (cachedResults != null)
+	    return cachedResults;
 
 	String result = (String)doCall ("retrieveObjectNames",
 					new Object[] {});
@@ -732,14 +828,13 @@
 	    for (int j = 0; j < children.getLength(); j++) {
 		if (children.item (j).getNodeName().equals ("Description")) {
 		    results.put (elem.getAttribute ("name"),
-				 children.item (j).getFirstChild().getNodeValue());
+				 getFirstValue (children.item (j)));
 		    break;
 		}
 	    }
 	}
 
-	if (useCache)
-	    cache.put("retrieveObjectNames", results);
+	setContents (cacheId, results);
 	return results;
     }
 
@@ -769,22 +864,36 @@
     public MobyDataType getDataType (String dataTypeName)
 	throws MobyException, NoSuccessException {
 
-	// See if we've already retrieved the DataType and cached it
-	if (cache.containsKey("retrieveObjectDefinition" + dataTypeName))
-	    return (MobyDataType) cache.get("retrieveObjectDefinition" + dataTypeName);
+	String result = getDataTypeAsXML (dataTypeName);
+	return createDataTypeFromXML (result, dataTypeName);
+    }
 
-	String result =
+    protected String getDataTypeAsXML (String dataTypeName)
+	throws MobyException, NoSuccessException {
+
+	String cacheId = "retrieveObjectDefinition_" + dataTypeName;
+	String cachedResults = (String)getContents (cacheId);
+	if (cachedResults != null)
+	    return cachedResults;
+
+	String results =
 	    (String)doCall ("retrieveObjectDefinition",
 			    new Object[] {
 				"<retrieveObjectDefinition>" +
 				  "<objectType>" + dataTypeName + "</objectType>" +
 				"</retrieveObjectDefinition>"
 			    });
+	setContents (cacheId, results);
+	return results;
+    }
+
+    protected MobyDataType createDataTypeFromXML (String xmlSource, String dataTypeName)
+	throws MobyException, NoSuccessException {
 
 	// parse returned XML
 	Document document = null;
 	try {
-	    document = docBuilder.parse(new StringBufferInputStream(result));
+	    document = docBuilder.parse(new StringBufferInputStream (xmlSource));
 	} catch (Exception e) {
 	    throw new MobyException(e.toString());
 	}
@@ -801,7 +910,7 @@
 	for (int j = 0; j < children.getLength(); j++) {
 	    String nodeName = children.item (j).getNodeName();
 	    if (nodeName.equals ("objectType")) {
-		data = new MobyDataType (children.item (j).getFirstChild().getNodeValue());
+		data = new MobyDataType (getFirstValue (children.item (j)));
 		break;
 	    }
 	}
@@ -814,11 +923,11 @@
 	for (int j = 0; j < children.getLength(); j++) {
 	    String nodeName = children.item (j).getNodeName();
 	    if (nodeName.equals ("Description")) {
-		data.setDescription (children.item (j).getFirstChild().getNodeValue());
+		data.setDescription (getFirstValue (children.item (j)));
 	    } else if (nodeName.equals ("authURI")) {
-		data.setAuthority (children.item (j).getFirstChild().getNodeValue());
+		data.setAuthority (getFirstValue (children.item (j)));
 	    } else if (nodeName.equals ("contactEmail")) {
-		data.setEmailContact (children.item (j).getFirstChild().getNodeValue());
+		data.setEmailContact (getFirstValue (children.item (j)));
 	    } else if (nodeName.equals ("Relationship")) {
 		String relationshipType = ((Element)children.item (j)).getAttribute ("relationshipType");
 		if (relationshipType.endsWith ("isa")) {
@@ -826,7 +935,7 @@
 		    NodeList parents = children.item (j).getChildNodes();
 		    for (int k = 0; k < parents.getLength(); k++) {
 			if (parents.item (k).getNodeName().equals ("objectType")) {
-			    data.addParentName (parents.item (k).getFirstChild().getNodeValue());
+			    data.addParentName (getFirstValue (parents.item (k)));
 			}
 		    }
 		} else if (relationshipType.endsWith ("hasa")) {
@@ -835,7 +944,7 @@
 		    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(),
+					    getFirstValue (belows.item (k)),
 					    Central.iHASA );
 			}
 		    }
@@ -852,7 +961,6 @@
 		}
 	    }
 	}
-	cache.put("retrieveObjectDefinition"+dataTypeName, data);
 	return data;
     }
 
@@ -878,10 +986,11 @@
      *************************************************************************/
     public String getServiceWSDL (String serviceName, String authority)
 	throws MobyException, NoSuccessException {
-	// See if we've already retrieved the DataType and cached it
-	String cacheKey = "getServiceWSDL" + serviceName + ":" + authority;
-	if(cache.containsKey(cacheKey))
-	    return (String) cache.get(cacheKey);
+
+	String cacheId = "getServiceWSDL" + serviceName + ":" + authority;
+	String cachedResults = (String)getContents (cacheId);
+	if (cachedResults != null)
+	    return cachedResults;
 	
 	String result =
 	    (String)doCall ("retrieveService",
@@ -901,9 +1010,10 @@
 	if (wsdl == null)
 	    throw new NoSuccessException ("Service not found OR WSDL is not available.",
 					   serviceName + " (" + authority + ")");
-	if(useCache)
-	    cache.put(cacheKey, wsdl.getNodeValue());
-	return wsdl.getNodeValue();
+
+	String results = wsdl.getNodeValue();
+	setContents (cacheId, results);
+	return results;
     }
 
     /*************************************************************************
@@ -1168,27 +1278,10 @@
     public MobyService[] findService (MobyService pattern, String[] keywords)
 	throws MobyException {
 	return findService (pattern, keywords, true, true);
-// 	if (pattern == null)
-// 	    pattern = new MobyService ("dummy");
-
-// 	String[] query = new String[] { 
-// 				"<findService>" +
-// 				buildQueryObject (pattern, keywords, true, true, false) + 
-// 				"</findService>"
-// 	};
-// 	if(useCache && cache.containsKey("findService"+query[0]))
-// 	    return (MobyService[]) cache.get("findService"+query[0]);
-	
-// 	String result = (String) doCall ("findService", query);
-// 	MobyService[] services = extractServices (result);
-
-// 	if(useCache)
-// 	    cache.put("findService"+query[0], services);
-// 	return services;
     }
 
     /**************************************************************************
-     *
+     * All 'findService' methods end up here...
      *************************************************************************/
     public MobyService[] findService (MobyService pattern, String[] keywords,
 				      boolean includeChildrenServiceTypes,
@@ -1197,6 +1290,17 @@
 	if (pattern == null)
 	    pattern = new MobyService ("dummy");
 
+	String result =
+	    getServicesAsXML (pattern, keywords, includeParentDataTypes, includeChildrenServiceTypes);
+	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,
@@ -1205,15 +1309,7 @@
 						  false) + 
 				"</findService>"
 	};
-	if(useCache && cache.containsKey("findService"+query[0]))
-	    return (MobyService[]) cache.get("findService"+query[0]);
-	
-	String result = (String) doCall ("findService", query);
-	MobyService[] services = extractServices (result);
-
-	if(useCache)
-	    cache.put("findService"+query[0], services);
-	return services;
+	return (String) doCall ("findService", query);
     }
 
     /**************************************************************************
@@ -1263,9 +1359,11 @@
     public String[] getServiceTypeRelationships (String serviceTypeName,
 						 boolean expand)
 	throws MobyException {
-	String cacheKey = "Relationships" + serviceTypeName + ":" + expand;
-	if(useCache && cache.containsKey(cacheKey))
-	    return (String[]) cache.get(cacheKey);
+
+	String cacheId = "Relationships_" + serviceTypeName + ":" + expand;
+	String[] cachedResults = (String[])getContents (cacheId);
+	if (cachedResults != null)
+	    return cachedResults;
 
 	String result =
 	    (String)doCall ("Relationships",
@@ -1289,15 +1387,14 @@
 	    NodeList children = elem.getChildNodes();
 	    for (int j = 0; j < children.getLength(); j++) {
 		if (children.item (j).getNodeName().equals ("serviceType")) {
-		    v.addElement (children.item (j).getFirstChild().getNodeValue());
+		    v.addElement (getFirstValue (children.item (j)));
 		}
 	    }
 	}
 	String[] results = new String [v.size()];
 	v.copyInto (results);
 
-	if(useCache)
-	    cache.put(cacheKey, results);
+	setContents (cacheId, results);
 	return results;
     }
 
@@ -1318,9 +1415,11 @@
      *************************************************************************/
     public Map getDataTypeRelationships (String dataTypeName)
 	throws MobyException {
-	String cacheKey = "getDataTypeRelationships"+dataTypeName;
-	if(useCache && cache.containsKey(cacheKey))
-	    return (Map) cache.get(cacheKey);
+
+	String cacheId = "getDataTypeRelationships_" + dataTypeName;
+	Map cachedResults = (Map)getContents (cacheId);
+	if (cachedResults != null)
+	    return cachedResults;
 	
 	String result =
 	    (String)doCall ("Relationships",
@@ -1349,7 +1448,7 @@
 	    Vector v = new Vector();
 	    for (int j = 0; j < children.getLength(); j++) {
 		if (children.item (j).getNodeName().equals ("objectType")) {
-		    v.addElement (children.item (j).getFirstChild().getNodeValue());
+		    v.addElement (getFirstValue (children.item (j)));
 		}
 	    }
 	    String[] names = new String [v.size()];
@@ -1357,8 +1456,7 @@
 	    results.put (relType, names);
 	}
 
-	if(useCache)
-	    cache.put(cacheKey, results);
+	setContents (cacheId, results);
 	return results;
     }
 
@@ -1376,9 +1474,11 @@
     public String[] getDataTypeRelationships (String dataTypeName,
 					      String relationshipType)
 	throws MobyException {
-	String cacheKey = "getDataTypeRelationships" + dataTypeName + ":" + relationshipType;
-	if(useCache && cache.containsKey(cacheKey))
-	    return (String[]) cache.get(cacheKey);
+
+	String cacheId = "getDataTypeRelationships_" + dataTypeName + ":" + relationshipType;
+	String[] cachedResults = (String[])getContents (cacheId);
+	if (cachedResults != null)
+	    return cachedResults;
 
 	String result =
 	    (String)doCall ("Relationships",
@@ -1404,37 +1504,32 @@
 	    NodeList children = elem.getChildNodes();
 	    for (int j = 0; j < children.getLength(); j++) {
 		if (children.item (j).getNodeName().equals ("objectType")) {
-		    v.addElement (children.item (j).getFirstChild().getNodeValue());
+		    v.addElement (getFirstValue (children.item (j)));
 		}
 	    }
 	}
 	String[] results = new String [v.size()];
 	v.copyInto (results);
 
-	if(useCache)
-	    cache.put(cacheKey, results);
+	setContents (cacheId, results);
 	return results;
     }
 
-    /**
-     * 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 requery
-     * MobyCentral every time)
-     */
-    public void setCacheMode(boolean shouldCache){
-	useCache = shouldCache;
-	if(!useCache)
-	    cache.clear();
+    /**************************************************************************
+     * Return an endpoint (a stringified URL) representing a Moby
+     * registry that an instance of this implementation is connected
+     * to.
+     *************************************************************************/
+    public String getRegistryEndpoint() {
+	return endpoint.toString();
     }
 
-    /**
-     * @return whether retrieveXXX calls will cache their responses
-     */
-    public boolean getCacheMode(){
-	return useCache;
+    /**************************************************************************
+     * Return a namespace (a URI) used by a Moby registry that an
+     * instance of this implementation is connected to.
+     *************************************************************************/
+    public String getRegistryNamespace() {
+	return uri;
     }
+
 }

===================================================================
RCS file: /home/repository/moby/moby-live/Java/src/main/org/biomoby/client/FileCache.java,v
retrieving revision 1.3
retrieving revision 1.4
diff -u -r1.3 -r1.4
--- /home/repository/moby/moby-live/Java/src/main/org/biomoby/client/FileCache.java	2004/04/01 21:03:42	1.3
+++ /home/repository/moby/moby-live/Java/src/main/org/biomoby/client/FileCache.java	2004/10/18 14:35:06	1.4
@@ -22,7 +22,7 @@
  * @version $Id$
  */
 public class FileCache
-    implements SimpleCache {
+    implements SimpleFileCache {
 
     protected static String CACHE_DIR = "cache";
     protected static String fileSeparator;
@@ -31,10 +31,9 @@
     protected String rootURLName;
 
     // a directory name (without a full path, usually without any path
-    // at all) where will be stored the cached object (as files) -
+    // at all) where the cached objects will be stored (as files) -
     // this directory is always added to the path given in the
-    // constructor (either relative to the servlet context or to the
-    // given 'rootDirname)
+    // constructor (relative to the given 'rootDirname')
     protected static String startingDir;
 
     // index of cached files (TBD: put it also in a file?)
@@ -50,12 +49,10 @@
 
     /**************************************************************************
      * Constructor specifying where to create files with the cached
-     * objects. Use this constructor if you cannot store them in the
-     * context of the calling servlet (usually because of the
-     * unsifficient write permissions). The cache files will be stored in
+     * objects. The cache files will be stored in
      *
      * <pre>
-     * <rootDirName>/cache/
+     * &lt;rootDirName&gt;/cache/
      * </pre>
      *
      * The all not yet existing directories (for example the last
@@ -117,15 +114,25 @@
     /**************************************************************************
      * It expects the 'id' in the form as created by 'createId'.
      **************************************************************************/
-    public boolean exists (String id) {
+    public boolean existsInCache (String id) {
 	return new File (getFullFilename (id)).exists();
     }
 
     /**************************************************************************
-     * Always returns true.
+     * This class does not implement this method. Use {@link
+     * #getFilename} and read the returned file yourself.
      **************************************************************************/
-    public boolean supportsFilenames() {
-	return true;
+    public Object getContents (String id)
+	throws IOException {
+	return null;
+    }
+
+    /**************************************************************************
+     * This class does not implement this method. Use {@link
+     * #setContents(String,byte[])} to store data in a file.
+     **************************************************************************/
+    public void setContents (String id, Object data)
+	throws IOException {
     }
 
     /**************************************************************************
@@ -159,15 +166,16 @@
      *
      * If a 'rootURLName' (given in the constructor) is not null it
      * returns:
-     *
-     *    <rootURLName>/<name>
-     *
-     * where <name> is a name relative to the beginning of the cache
+     *<pre>
+     *    &lt;rootURLName&gt;/&lt;name&gt;
+     *</pre>
+     * where &lt;name&gt; is a name relative to the beginning of the cache
      * (meaning that 'rootDirName' is not included here). If, however,
      * the 'rootURLName ' is null it returns:
-     *
-     *    file:<name>
-     * where the <name> is the same as above.
+     *<pre>
+     *    file:&lt;name&gt;
+     *</pre>
+     * where the &lt;name&gt; is the same as above.
      **************************************************************************/
     public String getURL (String id) {
 	if (rootURLName == null)
@@ -179,8 +187,9 @@
     /**************************************************************************
      *
      **************************************************************************/
-    public void remove (String id)
+    public void removeFromCache (String id)
 	throws IOException {
+	// TBD: not yet implemented
     }
 
     /**************************************************************************
@@ -188,6 +197,7 @@
      **************************************************************************/
     public void removeOlderThen (long millis)
 	throws IOException {
+	// TBD: not yet implemented
     }
 
     /**************************************************************************

===================================================================
RCS file: /home/repository/moby/moby-live/Java/src/main/org/biomoby/client/FilterServices.java,v
retrieving revision 1.1
retrieving revision 1.2
diff -u -r1.1 -r1.2
--- /home/repository/moby/moby-live/Java/src/main/org/biomoby/client/FilterServices.java	2003/11/08 00:27:24	1.1
+++ /home/repository/moby/moby-live/Java/src/main/org/biomoby/client/FilterServices.java	2004/10/18 14:35:06	1.2
@@ -157,6 +157,87 @@
     }
 
     /*************************************************************************
+     * Find all paths (using available 'edges') that start by any of
+     * the 'startringEdges' and finish by any of the 'endingEdges'.
+     * Each path always has one start and one end but can contain
+     * cycles, and forking and merging branches. <p>
+     *
+     * The number of returned pathes is equal or less than a product
+     * of number of 'startingEdges' and number of 'endingEdges'. <p>
+     *
+     * @see #straightDataPaths
+     *
+     * @param startingEdges where returned paths start
+     *
+     * @param edges are all available edges the returned paths are
+     * built from (including 'startingEdges' and 'endingEdges')
+     *
+     * @param endingEdges where returned paths finish
+     *
+     * @return an array where each element is a list of edges that
+     * constitute a path; it returns an empty array (not null) if
+     * there is no such path
+     *************************************************************************/
+    public static ServicesEdge[][] dataPaths (DataServiceEdge[] startingEdges,
+					      ServicesEdge[] edges,
+					      DataServiceEdge[] endingEdges) {
+	Vector v = new Vector();  // collect elements of type ServicesEdge[]
+	for (int i = 0; i < startingEdges.length; i++) {
+	    MobyService fromService = startingEdges[i].getService();
+	    if (fromService == null)
+		continue;  // it should not happen :-)
+	    for (int j = 0; j < endingEdges.length; j++) {
+		MobyService toService = endingEdges[j].getService();
+		if (toService == null)
+		    continue;  // it should not happen :-)
+		ServicesEdge[] onePath = pathes (edges,
+						 fromService.getName(),
+						 toService.getName());
+		if (onePath == null)
+		    continue;   // this happens; there is no connection between these two services
+
+		// a path found - put together its starting edge, all
+		// edges in between, and its ending edge
+		ServicesEdge[] fullOnePath = new ServicesEdge [onePath.length + 2];
+		fullOnePath [0] = startingEdges [i];
+		fullOnePath [1] = endingEdges [j];
+		System.arraycopy (onePath, 0, fullOnePath, 2, onePath.length);
+		v.addElement (fullOnePath);
+	    }
+	}
+	ServicesEdge[][] result = new ServicesEdge [v.size()][];
+	v.copyInto (result);
+	return result;
+    }
+
+    /*************************************************************************
+     * Join together several paths. It makes sure that the result does
+     * not have duplicated edges (edges with the same source, target,
+     * connector and perhaps other characteristics). <p>
+     *
+     * @param paths an array of paths (each paths is an array of
+     * edges) that will be joined
+     *
+     * @return a joint path
+     *************************************************************************/
+    public static ServicesEdge[] joinPaths (ServicesEdge[][] paths) {
+	HashSet edgeIds = new HashSet();
+	Vector v = new Vector();   // collect elements of type ServiceEdge
+	for (int i = 0; i < paths.length; i++) {
+	    for (int j = 0; j < paths[i].length; j++) {
+		String id = paths[i][j].getId();
+		if (edgeIds.contains (id))
+		    continue;
+		edgeIds.add (id);
+		v.addElement (paths[i][j]);
+	    }
+	}
+	ServicesEdge[] result = new ServicesEdge [v.size()];
+	v.copyInto (result);
+	return result;
+    }
+
+    /*************************************************************************
      * Do the recursive job of finding all matching pathes between
      * 'sourceServiceName' and any of the names in 'targets'. All
      * available edges are in 'edges'. The 'onThePath' contains
@@ -222,4 +303,105 @@
 	return results;
     }
 
+    /*************************************************************************
+     * Find all paths (using available 'edges') that start by any of
+     * the 'startringEdges' and finish by any of the 'endingEdges'.
+     * Each path always has one start and one end and it has no
+     * cycles and no branches. <p>
+     *
+     * The number of returned pathes is equal or less than a product
+     * of number of 'startingEdges' and number of 'endingEdges'. <p>
+     *
+     * @see #dataPaths
+     *
+     * @param startingEdges where returned paths start
+     *
+     * @param edges are all available edges the returned paths are
+     * built from (including 'startingEdges' and 'endingEdges')
+     *
+     * @param endingEdges where returned paths finish
+     *
+     * @return an array where each element is a list of edges that
+     * constitute a path; it returns an empty array (not null) if
+     * there is no such path
+     *************************************************************************/
+    public static ServicesEdge[][] straightDataPaths (DataServiceEdge[] startingEdges,
+						      ServicesEdge[] edges,
+						      DataServiceEdge[] endingEdges) {
+	// create a list with all edges (that have a source service)
+	// (actually, this eliminates 'startingEdges' plus some
+	// non-complete edges - can they exists?)
+	Vector v = new Vector();
+	for (int i = 0; i < edges.length; i++) {
+	    MobyService sourceService = edges[i].getSourceService();
+	    if (sourceService != null)
+		v.addElement (edges[i]);
+	}
+	ServicesEdge[] allEdges = new ServicesEdge [v.size()];
+	v.copyInto (allEdges);
+
+	// find all (no-cycle-containing) paths (recursively)
+	Vector allPaths = new Vector();
+	findAllPaths (startingEdges, allEdges, new Hashtable(), allPaths);
+	ServicesEdge[][] result = new ServicesEdge [allPaths.size()][];
+	allPaths.copyInto (result);
+	return result;
+    }
+
+    /*************************************************************************
+     * Does the real job, recursivelly...
+     *************************************************************************/
+    static void findAllPaths (ServicesEdge[] startingEdges,
+			      ServicesEdge[] allEdges,
+			      Hashtable soFarIncluded, // key: target service name
+			      Vector foundPaths) {     // elements of type ServicesEdge[]
+
+	for (int i = 0; i < startingEdges.length; i++) {
+
+	    // is this the final edge (ending in a target data type)?
+ 	    if ( (startingEdges[i] instanceof DataServiceEdge) &&
+ 		 ((DataServiceEdge)startingEdges[i]).isEndingEdge() ) {
+		ServicesEdge[] onePath = new ServicesEdge [soFarIncluded.size() + 1];
+		int e = 0;
+		for (Enumeration en = soFarIncluded.elements(); en.hasMoreElements(); )
+		    onePath [e++] = (ServicesEdge)en.nextElement();
+		onePath[e] = startingEdges[i];
+		foundPaths.addElement (onePath);
+		continue;
+	    }
+
+	    // no, not yet; so find where else it leads
+	    MobyService toService = startingEdges[i].getTargetService();
+	    if (toService == null)
+		continue;  // it should not happen :-)
+	    String toServiceName = toService.getUniqueName();
+
+	    // is this edge going to a node that is already included?
+	    if (soFarIncluded.containsKey (toServiceName)) {
+		continue;
+	    }
+
+	    // now we have an edge going further, so add it to the
+	    // so-far-included and...
+	    soFarIncluded.put (toServiceName, startingEdges[i]);
+
+	    // ...list all edges continuing from this edge and...
+	    Vector v = new Vector();
+	    for (int e = 0; e < allEdges.length; e++) {
+		if (allEdges[e].getSourceService().getUniqueName().equals (toServiceName))
+		    v.addElement (allEdges[e]);
+	    }
+	    ServicesEdge[] contEdges = new ServicesEdge [v.size()];
+	    v.copyInto (contEdges);
+
+	    // ...call itself recursively
+	    findAllPaths (contEdges, allEdges, soFarIncluded, foundPaths);
+
+	    // because this edge was just explored (perhaps one or
+	    // more paths were added to 'foundPaths') I remove it from
+	    // 'soFarIncluded'
+	    soFarIncluded.remove (toServiceName);
+	}
+    }
+
 }

===================================================================
RCS file: /home/repository/moby/moby-live/Java/src/main/org/biomoby/client/GraphsServlet.java,v
retrieving revision 1.6
retrieving revision 1.7
diff -u -r1.6 -r1.7
--- /home/repository/moby/moby-live/Java/src/main/org/biomoby/client/GraphsServlet.java	2004/04/01 23:24:27	1.6
+++ /home/repository/moby/moby-live/Java/src/main/org/biomoby/client/GraphsServlet.java	2004/10/18 14:35:06	1.7
@@ -46,6 +46,7 @@
     static final protected String PROXY_PORT = "http.proxyPort";
     static final protected String PROXY_HOST = "http.proxyHost";
     static final protected String DOT_PATH = "dot_path";
+    static final protected String REGISTRY_CACHE_DIR = "registry_cache_dir";
 
     // expected/known form's element names
     static final protected String VERBOSE = "verbose";
@@ -63,6 +64,11 @@
     static final protected String SEL_SERVI_1 = "sel_servi1";
     static final protected String SEL_SERVI_2 = "sel_servi2";
     static final protected String SEL_AUTH_MULTI = "sel_auth_multi";
+    static final protected String SEL_DATA_1 = "sel_data1";
+    static final protected String SEL_DATA_2 = "sel_data2";
+    static final protected String SEL_NS_1 = "sel_ns1";
+    static final protected String SEL_NS_2 = "sel_ns2";
+    static final protected String FILTER_DATAPATH = "datapath";
     static final protected String FILTER_PATH = "path";
     static final protected String FILTER_SELECT = "select";
     static final protected String FILTER_FULL = "full";
@@ -85,6 +91,7 @@
     static final protected String LINK_COLOR  = "#0000FF";
     static final protected String VLINK_COLOR = "#0000FF";
     static final protected String TEXT_COLOR  = "#000000";
+    static final protected String COL_HEADER  = "#FFCC00";
 
     // some default values
     static final protected long DEFAULT_REFRESH_IN_MINUTES = 360;
@@ -107,6 +114,11 @@
     static final protected String T_PLAIN = "txt";
     static final protected String T_RDF   = "rdf";
 
+    // and few other constants
+    static final protected String WITHOUT_NAMESPACE = "(no namespace)";
+    static final protected String RESULTWIN = "RESULTWIN";
+
+
     // table containing mapping between several Moby registries and
     // instances of this class:
     //    key (type String):          URL of Moby registries + "::" + its namespaces
@@ -124,37 +136,50 @@
     static Hashtable contentTypes;
     static Hashtable displayNamesForTypes;
 
-    // cache for created graphs
-    static SimpleCache cache;
-
+    // cache for created graphs (images)
+    static SimpleFileCache cache;
     static ServletContext context;
+
+    // cache location for all Moby registries
+    static String registryCacheDir;
+
+    // default Moby registry
     static String defaultEndpoint;
     static String defaultNamespace;
 
     // definition of a Moby registry
-    String endpoint;
-    String namespace;
-    Central registry;
+//     String endpoint;
+//     String namespace;
+    CentralAll registry;
     boolean debug = false;
     boolean verbose = false;
-    MobyDataType[] dataTypes;
-    MobyService[] services;
-    MobyServiceType[] serviceTypes;
+//     MobyDataType[] dataTypes;
+//     MobyService[] services;
+//     MobyServiceType[] serviceTypes;
     long lastRead = -1;         // in millis
     long refreshInterval = -1;  // in millis
 
     /*************************************************************************
-     *
+     * A default constructor, used usually by a servlet engine.
      *************************************************************************/
     public GraphsServlet() {
     }
 
     /*************************************************************************
-     *
+     * A real constructor that is called when it is known what Moby
+     * registry we are going to serve for. All instances of
+     * GraphsServlet are stored in a hashtable so when a call for
+     * already known combination of 'mobyEndpoint' and 'mobyNamespace'
+     * comes a proper instance of GraphsServlet is either restored from
+     * the hashtable, or created.
      *************************************************************************/
-    public GraphsServlet (String mobyEndpoint, String mobyNamespace) {
-	endpoint = mobyEndpoint;
-	namespace = mobyNamespace;
+    public GraphsServlet (String mobyEndpoint, String mobyNamespace)
+	throws MobyException {
+
+	// note that 'registryCacheDir' is shared by all registries -
+	// and that's why it could be created already in init()
+	registry =
+	    new CentralDigestCachedImpl (mobyEndpoint, mobyNamespace, registryCacheDir);
     }
 
     /**************************************************************************
@@ -224,6 +249,13 @@
 	if (UUtils.isEmpty (defaultNamespace))
 	    defaultNamespace = CentralImpl.DEFAULT_NAMESPACE;
 
+	registryCacheDir = (String)initParams.get (REGISTRY_CACHE_DIR);
+	if (UUtils.isEmpty (registryCacheDir)) {
+	    String tmpDir = System.getProperty ("java.io.tmpdir");
+	    String fileSeparator = System.getProperty ("file.separator");
+	    registryCacheDir = tmpDir + fileSeparator + "mobycache";
+	}
+
 	// set HTTP proxy - this is probably useless because (I guess)
 	// the proxy can be set in the Tomcat configuration file (and
 	// not to let each servlet to do it) - but it's here, anyway
@@ -257,6 +289,14 @@
 	if (cache == null)
 	    cache = initCache (context, req.getContextPath());
 
+	// set some admin options (keep their old values if they are not sent)
+	if (getString (req, VERBOSE) != null)
+	    verbose = getBoolean (req, VERBOSE);
+	if (getString (req, DEBUG) != null) {
+	    debug = getBoolean (req, DEBUG);
+	    if (debug) verbose = true;
+	}
+
 	// for each Moby Registry (identified by its URL), there is an
 	// instance of this class (because it keeps some data about
 	// its registry cached) - there is a global table 'registries'
@@ -270,32 +310,25 @@
 	String key = endpoint + "::" + namespace;
 	GraphsServlet worker = (GraphsServlet)registries.get (key);
 
-	// set some admin options (if they are not sent keep their old value)
-	if (getString (req, VERBOSE) != null)
-	    verbose = getBoolean (req, VERBOSE);
-	if (getString (req, DEBUG) != null) {
-	    debug = getBoolean (req, DEBUG);
-	    if (debug) verbose = true;
-	}
-
 	// working the first time with this registry
 	if (worker == null) {
-	    worker = new GraphsServlet (endpoint, namespace);
-	    worker.setVerbose (verbose);
-	    worker.setDebug (debug);
-	    registries.put (key, worker);
-	}
-
-	// maybe someone wants to re-read the registry
-	if (getBoolean (req, REFRESH)) {
 	    try {
-		worker.readRegistry();
+		worker = new GraphsServlet (endpoint, namespace);
+		worker.setVerbose (verbose);
+		worker.setDebug (debug);
+		worker.registry.setDebug (debug);
+		registries.put (key, worker);
 	    } catch (MobyException e) {
 		error (res, res.SC_SERVICE_UNAVAILABLE, e);
 		return;
 	    }
 	}
 
+	// maybe someone wants to re-read the registry
+	if (getBoolean (req, REFRESH)) {
+	    ((CentralDigestImpl)worker.registry).removeFromCache (null);
+	}
+
 	// do the main job
 	if (exists (req, ACTION_MAIN_FORM))
 	    worker.doFormPage (req, res);
@@ -332,9 +365,9 @@
 
 	out.println (h.gen (P, "What BioMoby registry do you wish to explore? The URL is essential - but usually the default value is fine. The namespace (also sometimes called URI) mostly does not need to be changed."));
 	out.println ("URL of the BioMoby registry: ");
-	out.println (h.gen (P, h.text (REGISTRY_URL, endpoint, 50)));
+	out.println (h.gen (P, h.text (REGISTRY_URL, registry.getRegistryEndpoint(), 50)));
 	out.println ("Namespace of the registry: ");
-	out.println (h.gen (P, h.text (REGISTRY_URI, namespace, 50)));
+	out.println (h.gen (P, h.text (REGISTRY_URI, registry.getRegistryNamespace(), 50)));
 
  	out.println (h.gen (P, "The graphs are created faster if information about the registry are not retrieved again and again for each request. But if you wish to get really the most latest state check the box below."));
 	out.println (h.checkbox (REFRESH));
@@ -356,8 +389,15 @@
     protected void doFormPage (HttpServletRequest req, HttpServletResponse res)
 	throws ServletException, IOException {
 
+	MobyService[] services = null;
+	MobyDataType[] dataTypes = null;
+	Map namespaces = null;
 	try {
 	    readRegistryIfNeeded();
+	    services = registry.getServices();
+	    dataTypes = registry.getDataTypes();
+	    namespaces  = registry.getNamespaces();
+	    lastRead = getLastRead();
 	} catch (MobyException e) {
  	    error (res, res.SC_SERVICE_UNAVAILABLE, e);
 	    return;
@@ -379,8 +419,8 @@
 	out.println (h.gen (H1, "Exploring BioMoby graphically"));
 	out.println (h.gen (FONT, new String[] { SIZE, "-1" },
 			    h.gen (BLOCKQUOTE,
-				   "BioMoby registry: " + endpoint + "<br>" +
-				   "Last re-read: " + new Date (lastRead).toString())));
+				   "BioMoby registry: " + registry.getRegistryEndpoint() + "<br>" +
+				   "Last re-read: " + (lastRead <= 0 ? "unknown" : new Date (lastRead).toString()))));
 	out.println (h.gen (P, "Specify below what kind of graph should be created and what properties it should have. Notice that some properties are shared by all graph types - look at the end of the page."));
 
 	// ----------------------------------------------------
@@ -471,17 +511,73 @@
 		    new String[] { "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "MAX" },
 		    null, selected);
 
+	// lists of data types (and namespaces) - for selecting a datapath
+	String checked3 = "document.forms[0]." + FILTER + "[3].checked = '1';";
+
+	String[] dataLabels = new String [dataTypes.length];
+	for (int i = 0; i < dataTypes.length; i++)
+	    dataLabels[i] = Utils.pureName (dataTypes[i].getName());
+	Arrays.sort (dataLabels);
+
+	selected.clear();
+	previouslySelected = getString (req, SEL_DATA_1);
+	if (previouslySelected != null)
+	    selected.put (previouslySelected, "1");
+	attrs.clear();
+	attrs.put (NAME, SEL_DATA_1);
+	attrs.put (ONCHANGE, checked3);
+	String selectData1 = h.list (attrs, dataLabels, null, selected);
+
+	selected.clear();
+	previouslySelected = getString (req, SEL_DATA_2);
+	if (previouslySelected != null)
+	    selected.put (previouslySelected, "1");
+	attrs.clear();
+	attrs.put (NAME, SEL_DATA_2);
+	attrs.put (ONCHANGE, checked3);
+	String selectData2 = h.list (attrs, dataLabels, null, selected);
+
+	Vector v = new Vector();
+	for (Iterator it = namespaces.entrySet().iterator(); it.hasNext(); ) {
+	    Map.Entry entry = (Map.Entry)it.next();
+	    v.addElement (entry.getKey());
+	}
+	new Sorter().sort (v);
+	v.insertElementAt (WITHOUT_NAMESPACE, 0);
+	String[] nsLabels = new String [v.size()];
+	v.copyInto (nsLabels);
+
+	selected.clear();
+	previouslySelected = getString (req, SEL_NS_1);
+	if (previouslySelected != null)
+	    selected.put (previouslySelected, "1");
+	attrs.clear();
+	attrs.put (NAME, SEL_NS_1);
+	attrs.put (ONCHANGE, checked3);
+	String selectNs1 = h.list (attrs, nsLabels, null, selected);
+
+	selected.clear();
+	previouslySelected = getString (req, SEL_NS_2);
+	if (previouslySelected != null)
+	    selected.put (previouslySelected, "1");
+	attrs.clear();
+	attrs.put (NAME, SEL_NS_2);
+	attrs.put (ONCHANGE, checked3);
+	String selectNs2 = h.list (attrs, nsLabels, null, selected);
+
 	// the main radio group (what to do)
-	int checked = 0;
+	int checked = 3;
 	previouslySelected = getString (req, FILTER);
 	if (previouslySelected != null) {
 	    if (previouslySelected.equals (FILTER_SELECT))
 		checked = 1;
 	    else if (previouslySelected.equals (FILTER_PATH))
 		checked = 2;
+	    else if (previouslySelected.equals (FILTER_DATAPATH))
+		checked = 3;
 	}
 	String[] radios = radioGroup (h, FILTER,
-				      new String[] { FILTER_FULL, FILTER_SELECT, FILTER_PATH },
+				      new String[] { FILTER_FULL, FILTER_SELECT, FILTER_PATH, FILTER_DATAPATH },
 				      checked);
 
 	// and put everything together
@@ -518,7 +614,35 @@
 					 h.gen (TR,
 						h.gen (TD, selectService1) +
 						h.gen (TD, h.gen (B, " <--> ")) +
-						h.gen (TD, selectService2)))))));
+						h.gen (TD, selectService2))))) +
+		    h.gen (TR,
+			   h.gen (TD, new String[] { VALIGN, "top" },
+				  radios[3]) +
+			   h.gen (TD, h.gen (IMG, new String[] { BORDER, "0",
+								 ALIGN, "left",
+								 SRC, req.getContextPath() + "/images/new.gif" }) +
+				  "Show only pathes between two selected data types<br>" +
+				  "(e.g. GO/Object --&gt; AminoAcidSequence)")) +
+		    h.gen (TR,
+			   h.gen (TD, "&nbsp;") +
+			   h.gen (TD,
+				  h.gen (TABLE,
+					 h.gen (TR,
+						h.gen (TD, "Namespace: ") +
+						h.gen (TD, selectNs1 + " /") +
+						h.gen (TD, ROWSPAN, "2", h.gen (B, " --> ")) +
+						h.gen (TD, "Namespace: ") +
+						h.gen (TD, selectNs2 + " /")) +
+					 h.gen (TR,
+						h.gen (TD, "Data type: ") +
+						h.gen (TD, selectData1) +
+						h.gen (TD, "Data type: ") +
+						h.gen (TD, selectData2))
+					 )
+				  )
+			   )
+			)
+	       );
 
  	out.println (h.gen (P,
 			    h.submit (" Create Graph of Service Instances ", ACTION_JOB_SERVI)));
@@ -595,6 +719,9 @@
     protected void doGraphServices (HttpServletRequest req, HttpServletResponse res)
 	throws ServletException, IOException {
 
+	// this says what type of service graph is wanted
+	String filter = getString (req, FILTER);
+
 	// check if the output type is supported
 	String wantedOutputType = getString (req, OUTPUT_TYPE_SERVI);
 	if (wantedOutputType == null)
@@ -611,6 +738,11 @@
 		   new MobyException ("Unrecognized output type '" + wantedOutputType + "'."));
 	    return;
 	}
+	if (filter.equals (FILTER_DATAPATH) && wantedOutputType.equals (T_RDF)) {
+	    error (res, res.SC_SERVICE_UNAVAILABLE,
+		   new MobyException ("Sorry, RDF type for graphs of data paths is not supported."));
+	    return;
+	}
 
 	// collect visualization properties
 	Properties props = new Properties();
@@ -618,14 +750,18 @@
 	if (rankdir != null)
 	    props.put (Graphviz.PROP_RANKDIR, rankdir);
 
+	// this is to distinguish also the cached HTML pages
+	props.put ("wot", wantedOutputType);
+
 	// add other properties - to be used for caching etc.
-	String filter = getString (req, FILTER);
 	props.put (FILTER, filter);
 	String[] authorities = null;
 	String[] serviceNames = null;
 	int depth = 1;
 	String s1 = null;
 	String s2 = null;
+	String s3 = null;
+	String s4 = null;
 	if (filter.equals (FILTER_SELECT)) {
 	    authorities = req.getParameterValues (SEL_AUTH_MULTI);
 	    serviceNames = req.getParameterValues (SEL_SERVI_MULTI);
@@ -652,55 +788,499 @@
 		props.put (SEL_SERVI_1, s1);
 		props.put (SEL_SERVI_2, s2);
 	    }
+
+	} else if (filter.equals (FILTER_DATAPATH)) {
+	    s1 = getString (req, SEL_DATA_1);
+	    s2 = getString (req, SEL_DATA_2);
+	    s3 = getString (req, SEL_NS_1);
+	    s4 = getString (req, SEL_NS_2);
+	    if (s1 != null && s2 != null && s3 != null && s4 != null) {
+		props.put (SEL_DATA_1, s1);
+		props.put (SEL_DATA_2, s2);
+		props.put (SEL_NS_1, s3);
+		props.put (SEL_NS_2, s4);
+	    }
+	}
+
+	if (filter.equals (FILTER_SELECT)) {
+	    if ( (authorities == null || authorities.length == 0) &&
+		 (serviceNames == null || serviceNames.length == 0) ) {
+		error (res, res.SC_SERVICE_UNAVAILABLE,
+		       new MobyException ("Please, select at least one authority or one service name."));
+		return;
+	    }
 	}
 
 	try {
+	    // make sure that we have data from a registry (it is
+	    // either already in the registry cache - so it does not
+	    // take long to read it, or it is not - so it is good to
+	    // refill the cache and to get the new 'lastRead'
+	    // timestamp)
+	    MobyService[] services = registry.getServices();
+	    MobyDataType[] dataTypes = registry.getDataTypes();
+	    lastRead = getLastRead();
+
 	    // perhaps we have the same graph in the cache already
-	    String id = cache.createId (endpoint, GRAPH_SERVI,
- 					wantedOutputType, lastRead, props);
-	    if (! cache.exists (id)) {
-		// create a [dot,rdf] definition of the graph
-		log ("Creating a graph of the service instances...\n");
-		ServicesEdge[] edges = ServiceConnections.build (dataTypes, services);
-
-		// filter edges
-		if (filter.equals (FILTER_SELECT)) {
-		    edges = FilterServices.filter (edges, authorities, serviceNames, depth);
-
-		} else if (filter.equals (FILTER_PATH)) {
-		    if (s1 != null && s2 != null) {
-			edges = FilterServices.pathes (edges, s1, s2);
-			if (edges == null) {
-			    // no connection found between services s1 and s2
-			    Vector v = new Vector();
-			    for (int i = 0; i < services.length; i++) {
-				if (services[i].getName().equals (s1) ||
-				    services[i].getName().equals (s2))
-				    v.addElement (new ServicesEdge (services[i],
-								    ServicesEdge.NO_CONNECTION));
-			    }
-			    edges = new ServicesEdge [v.size()];
-			    v.copyInto (edges);
-			}
-		    }
+	    // (but not for DATAPATH - there it is more complicated)
+	    String graphId = cache.createId (registry.getRegistryEndpoint(), GRAPH_SERVI,
+					     wantedOutputType, lastRead, props);
+	    String pageId = cache.createId (registry.getRegistryEndpoint(), GRAPH_SERVI,
+					    "html", lastRead, props);
+	    if ( cache.existsInCache (graphId) &&
+		 cache.existsInCache (pageId) &&
+		 ! filter.equals (FILTER_DATAPATH) ) {
+		res.sendRedirect (res.encodeRedirectURL (cache.getURL (pageId)));
+		return;
+	    }
+
+	    // create all edges between all services
+	    log ("Creating a graph of the service instances...\n");
+	    ServicesEdge[] edges = ServiceConnections.build (dataTypes, services);
+
+	    // create an HMTL static page (and the graps the page is
+	    // linked to)
+	    if (filter.equals (FILTER_DATAPATH)) {
+		doDataPathResultPage (pageId, wantedOutputType, req, edges, dataTypes, services, props);
+
+	    } else {
+		doServicesResultPage (pageId, graphId, wantedOutputType, req, edges,
+				      filter, authorities, serviceNames, depth, s1, s2, props);
+	    }
+	    res.sendRedirect (res.encodeRedirectURL (cache.getURL (pageId)));
+
+	} catch (MobyException e) {
+	    error (res, res.SC_SERVICE_UNAVAILABLE, e);
+	    return;
+	}
+    }
+
+    /*************************************************************************
+     * Create a page containing results for a service graph.
+     *************************************************************************/
+    protected void doServicesResultPage (String pageId, String graphId, String wantedOutputType,
+					 HttpServletRequest req,
+					 ServicesEdge[] edges,
+					 String filter, String[] authorities, String[] serviceNames, int depth,
+					 String service1, String service2,
+					 Properties props)
+	throws ServletException, IOException, MobyException {
+
+	// where (and by whom) to create resulting HTML page
+	String filename = cache.getFilename (pageId);
+	PrintWriter out = new PrintWriter (new FileOutputStream (filename));
+	Html h = new Html (req);
+
+	String title = "Services graph";
+
+	//
+	// filter edges according the given parameters
+	//
+	if (filter.equals (FILTER_SELECT)) {
+	    edges = FilterServices.filter (edges, authorities, serviceNames, depth);
+
+	} else if (filter.equals (FILTER_PATH)) {
+	    if (service1 != null && service2 != null) {
+		edges = FilterServices.pathes (edges, service1, service2);
+		if (edges == null) {
+		    // no connection found between services service1 and service2
+		    out.print (h.startHtml (new String[] {
+			TITLE, "Moby Graphs Services Graph page - no path found",
+			BGCOLOR, BG_COLOR,
+			LINK, LINK_COLOR, VLINK, VLINK_COLOR, TEXT, TEXT_COLOR }));
+
+		    out.println (h.gen (H1, title));
+		    out.println ("No connection found between service " +
+				 h.gen (B, service1) + " and service " +
+				 h.gen (B, service2));
+		    out.print (h.endHtml());		    
+		    out.close();
+		    return;
 		}
+	    }
+	}
+	int nos = getNumberOfServices (edges);
 
-		// create the real graphs (or an RDF representation)
-		if (wantedOutputType.equals (T_RDF)) {
-		    String graph = RDF.createServicesGraph (edges, props);
-		    cache.setContents (id, graph.getBytes());
+	//
+	// create a graph (or an RDF representation)
+	//
+	if (wantedOutputType.equals (T_RDF)) {
+	    String graph = RDF.createServicesGraph (edges, props);
+	    cache.setContents (graphId, graph.getBytes());
 
-		} else {
-		    createGraph (id, wantedOutputType,
-				 Graphviz.createServicesGraph (edges, props));
+	} else {
+	    createGraph (graphId, wantedOutputType,
+			 Graphviz.createServicesGraph (edges, props));
+	}
+	long fsize = new File (cache.getFilename (graphId)).length();
+
+	//
+	// create the resulting page
+	//
+	out.print (h.startHtml (new String[] {
+	    TITLE, "Moby Graphs Services page",
+	    BGCOLOR, BG_COLOR,
+	    LINK, LINK_COLOR, VLINK, VLINK_COLOR, TEXT, TEXT_COLOR }));
+
+	out.println (h.a (cache.getURL (graphId), RESULTWIN, h.gen (H1, title)));
+	out.println (h.gen (BLOCKQUOTE,
+			    h.gen (FONT, SIZE, "-1",
+				   "Click on the header above to get the whole graph. WARNING: Please take file size into account before downloading! Do not try to open too large image in your browser. Save the file to your computer instead.")));
+
+	StringBuffer buf = new StringBuffer();
+	if (filter.equals (FILTER_SELECT)) {
+	    if (authorities != null && authorities.length > 0) {
+		StringBuffer b = new StringBuffer();
+		for (int i = 0; i < authorities.length; i++) {
+		    b.append (authorities[i] + "<br>");
 		}
+		buf.append (h.gen (TR,
+				   h.gen (TD, VALIGN, "top", h.gen (B, "Selected authorities: ")) +
+				   h.gen (TD, new String (b))));
 	    }
-	    res.sendRedirect (res.encodeRedirectURL (cache.getURL (id)));
+	    if (serviceNames != null && serviceNames.length > 0) {
+		StringBuffer b = new StringBuffer();
+		for (int i = 0; i < serviceNames.length; i++) {
+		    b.append (serviceNames[i] + "<br>");
+		}
+		buf.append (h.gen (TR,
+				   h.gen (TD, VALIGN, "top", h.gen (B, "Selected services: ")) +
+				   h.gen (TD, new String (b))));
+	    }
+	    buf.append (h.gen (TR,
+			       h.gen (TD, h.gen (B, "Maximum neighbourhood level: ")) +
+			       h.gen (TD, ""+depth)));
 
-	} catch (MobyException e) {
-	    error (res, res.SC_SERVICE_UNAVAILABLE, e);
+	} else if (filter.equals (FILTER_PATH)) {
+	    if (service1 != null && service2 != null) {
+		buf.append (h.gen (TR,
+				   h.gen (TD, VALIGN, "top", h.gen (B, "Included only paths between services: ")) +
+				   h.gen (TD, service1 + "<br>" + service2)));
+	    }
+	}
+
+	out.println
+	    (h.gen (TABLE, new String[] { CELLPADDING, "5" },
+		    h.gen (TR,
+			   h.gen (TD, h.gen (B, "Number of included services: ")) +
+			   h.gen (TD, ""+nos)) +
+		    h.gen (TR,
+			   h.gen (TD, h.gen (B, "Number of edges between them: ")) +
+			   h.gen (TD, ""+edges.length)) +
+		    h.gen (TR,
+			   h.gen (TD, h.gen (B, "Image type: ")) +
+			   h.gen (TD, wantedOutputType)) +
+		    h.gen (TR,
+			   h.gen (TD, h.gen (B, "Image size: ")) +
+			   h.gen (TD, fsize + " B")) +
+		    new String (buf)
+		    ));
+
+ 	out.print (getSignature (h));
+	out.print (h.endHtml());		    
+	out.close();
+    }
+
+    /*************************************************************************
+     * Create a page containing results for one data path.
+     *************************************************************************/
+    protected void doDataPathResultPage (String pageId, String wantedOutputType,
+					 HttpServletRequest req,
+					 ServicesEdge[] edges,
+					 MobyDataType[] dataTypes, MobyService[] services,
+					 Properties props)
+	throws ServletException, IOException, MobyException {
+
+	// where (and by whom) to create resulting HTML page
+	String filename = cache.getFilename (pageId);
+	PrintWriter out = new PrintWriter (new FileOutputStream (filename));
+	Html h = new Html (req);
+
+	// what we got as input arguments
+	String s1 = getString (req, SEL_DATA_1);
+	String s2 = getString (req, SEL_DATA_2);
+	String s3 = getString (req, SEL_NS_1);
+	String s4 = getString (req, SEL_NS_2);
+
+	if (s1 == null) s1 = "Object";
+	if (s2 == null) s2 = "Object";
+	if (s3 == null || s3.equals (WITHOUT_NAMESPACE)) s3 = "";
+	if (s4 == null || s4.equals (WITHOUT_NAMESPACE)) s4 = "";
+
+	String title = "Data path from " + (s3.equals ("") ? "" : (s3 + "/")) + s1 +
+	    " to " +  (s4.equals ("") ? "" : (s4 + "/")) + s2;
+
+	//
+	// do computing and edges creation
+	//
+	MobyPrimaryDataSimple sourceData = createSimpleData (s3, s1);
+	MobyPrimaryDataSimple targetData = createSimpleData (s4, s2);
+
+	DataServiceEdge[] startingEdges = ServiceConnections.findStartingEdges (sourceData, dataTypes, services);
+	DataServiceEdge[] endingEdges = ServiceConnections.findEndingEdges (targetData, dataTypes, services);
+
+	// this creates *all* pathes, but some of them have cycles and inside branches
+	ServicesEdge[][] separatePaths = FilterServices.dataPaths (startingEdges, edges, endingEdges);
+	if (separatePaths.length == 0) {
+	    out.print (h.startHtml (new String[] {
+		TITLE, "Moby Graphs Data Path page - no path found",
+		BGCOLOR, BG_COLOR,
+		LINK, LINK_COLOR, VLINK, VLINK_COLOR, TEXT, TEXT_COLOR }));
+
+	    out.println (h.gen (H1, title));
+	    out.println ("No connection found.");
+	    out.print (h.endHtml());		    
+	    out.close();
+	    return;
+	}
+	ServicesEdge[] allPaths = FilterServices.joinPaths (separatePaths);
+	
+	// this separate paths to straight paths (no cycles, no branches)
+	separatePaths = FilterServices.straightDataPaths (startingEdges, allPaths, endingEdges);
+
+	String theGraphURL = null;
+
+	// create separate graph definitions (plus several paths on a
+	// page, plus scufl definition for each individual path)
+	Properties tavernaPropsRaw = new Properties();
+	tavernaPropsRaw.put (Taverna.PROP_RAWINPUT, "true");
+	tavernaPropsRaw.put (Taverna.PROP_RAWOUTPUT, "true");
+	int pageSize = 5;
+	String[] scufls = new String [separatePaths.length];
+	String[] scuflRaws = new String [separatePaths.length];
+	String[] scuflURLs = new String [separatePaths.length];
+	String[] scuflRawURLs = new String [separatePaths.length];
+	String[] pathNames = new String [separatePaths.length];
+	String[] scuflNames = new String [separatePaths.length];
+	for (int i = 0; i < separatePaths.length; i++) {
+	    pathNames[i] = Printf.format ("Path %.2d", "" + (i+1));
+	    scuflNames[i] = Printf.format ("Scufl %.2d", "" + (i+1));
+	}
+	String[] graphs = new String [separatePaths.length];
+	String[] graphURLs = new String [separatePaths.length];
+	int[] nosInGraphs = new int [separatePaths.length];
+	int[] noeInGraphs = new int [separatePaths.length];
+	int pageBeginPos = 0;
+	int graphIndex = 0;
+	int numberOfJointGraphs = 0;
+	if (separatePaths.length > 0)
+	    numberOfJointGraphs = (separatePaths.length - 1) / pageSize + 1;
+	String[] jointGraphs = new String [numberOfJointGraphs];
+	String[] jointGraphURLs = new String [numberOfJointGraphs];
+	for (int i = 0; i < separatePaths.length; i++) {
+	    // a separate graph
+	    graphs[i] = Graphviz.createServicesGraph (separatePaths[i], props);
+	    nosInGraphs[i] = getNumberOfServices (separatePaths[i]);
+	    noeInGraphs[i] = separatePaths[i].length;
+	    // a joint graph: have we reached an end of a page?
+	    if ( (i+1) % pageSize == 0 || (i+1) == separatePaths.length ) {
+		// yes => create a graph containing only paths from 'pageBeginPos' to 'i'
+		jointGraphs [graphIndex++] =
+		    Graphviz.createServicesGraph (separatePaths, pageBeginPos, i, pathNames, props);
+		pageBeginPos = i+1;
+	    }
+	    // a scufl definition
+	    scufls[i] = Taverna.buildWorkflow (separatePaths[i], registry.getRegistryEndpoint(),
+					       new Properties());
+	    scuflRaws[i] = Taverna.buildWorkflow (separatePaths[i], registry.getRegistryEndpoint(),
+						  tavernaPropsRaw);
+	}
+
+	// create a definition for the whole graph
+	String theGraph = Graphviz.createServicesGraph (allPaths, props);
+	int nosTheGraph = getNumberOfServices (allPaths);
+	int noeTheGraph = allPaths.length;
+
+	//
+	// call 'dot'to produce images from the various sets of edges
+	// created abovce
+	//
+
+	String fileId;
+
+	// create the whole graph (image)
+	long fsizeTheGraph = 0;
+	if (theGraph != null) {
+	    fileId = cache.createId (registry.getRegistryEndpoint(), GRAPH_SERVI,
+				     wantedOutputType, lastRead, props);
+	    if (! cache.existsInCache (fileId))
+		createGraph (fileId, wantedOutputType, theGraph);
+	    theGraphURL = cache.getURL (fileId);
+	    fsizeTheGraph = new File (cache.getFilename (fileId)).length();
+	}
+
+	// create individual graphs (images); make sure that they have
+	// always a unique name because I cannot guarantee that the
+	// separated paths will be in the same order (for the same big
+	// graph) - it depends on the algoritm used by Hashtable which
+	// I do not know
+	if (graphs != null && graphs.length > 0) {
+	    for (int i = 0; i < graphs.length; i++) {
+
+		props.put ("UNIQUE", new java.rmi.server.UID().toString());
+		fileId = cache.createId (registry.getRegistryEndpoint(), GRAPH_SERVI,
+					 wantedOutputType, lastRead, props);
+		createGraph (fileId, wantedOutputType, graphs[i]);
+		graphURLs[i] = cache.getURL (fileId);
+	    }
+	}
+	if (jointGraphs != null && jointGraphs.length > 0) {
+	    for (int i = 0; i < jointGraphs.length; i++) {
+
+		props.put ("UNIQUE", new java.rmi.server.UID().toString());
+		fileId = cache.createId (registry.getRegistryEndpoint(), GRAPH_SERVI,
+					 wantedOutputType, lastRead, props);
+		createGraph (fileId, wantedOutputType, jointGraphs[i]);
+		jointGraphURLs[i] = cache.getURL (fileId);
+	    }
+	}
+	if (scufls != null && scufls.length > 0) {
+	    for (int i = 0; i < scufls.length; i++) {
+
+		props.put ("UNIQUE", new java.rmi.server.UID().toString());
+		fileId = cache.createId (registry.getRegistryEndpoint(), GRAPH_SERVI,
+					 "scufl", lastRead, props);
+		cache.setContents (fileId, scufls[i].getBytes());
+		scuflURLs[i] = cache.getURL (fileId);
+	    }
+	}
+	if (scuflRaws != null && scuflRaws.length > 0) {
+	    for (int i = 0; i < scuflRaws.length; i++) {
+
+		props.put ("UNIQUE", new java.rmi.server.UID().toString());
+		fileId = cache.createId (registry.getRegistryEndpoint(), GRAPH_SERVI,
+					 "scufl", lastRead, props);
+		cache.setContents (fileId, scuflRaws[i].getBytes());
+		scuflRawURLs[i] = cache.getURL (fileId);
+	    }
+	}
+	props.remove ("UNIQUE");
+
+	//
+	// put it all together and create the resulting page
+	//
+	out.print (h.startHtml (new String[] {
+	    TITLE, "Moby Graphs Data Path page",
+	    BGCOLOR, BG_COLOR,
+	    LINK, LINK_COLOR, VLINK, VLINK_COLOR, TEXT, TEXT_COLOR }));
+
+	out.println (h.a (theGraphURL, RESULTWIN, h.gen (H1, title)));
+	out.println (h.gen (BLOCKQUOTE,
+			    h.gen (FONT, SIZE, "-1",
+				   "Click on the header above to get the whole graph. WARNING: Please take image file size into account before downloading! Do not try to open too large image in your browser. Save the file to your computer instead.")));
+
+	out.println
+	    (h.gen (TABLE, new String[] { CELLPADDING, "5" },
+		    h.gen (TR,
+			   h.gen (TD, h.gen (B, "Number of involved services: ")) +
+			   h.gen (TD, ""+nosTheGraph)) +
+		    h.gen (TR,
+			   h.gen (TD, h.gen (B, "Number of edges between them: ")) +
+			   h.gen (TD, ""+noeTheGraph)) +
+		    h.gen (TR,
+			   h.gen (TD, h.gen (B, "Number of acyclic, non-forking pathes: ")) +
+			   h.gen (TD, ""+separatePaths.length)) +
+		    h.gen (TR,
+			   h.gen (TD, h.gen (B, "Image type: ")) +
+			   h.gen (TD, wantedOutputType)) +
+		    h.gen (TR,
+			   h.gen (TD, h.gen (B, "Image size: ")) +
+			   h.gen (TD, fsizeTheGraph + " B"))
+		    ));
+
+	if (separatePaths.length == 0) {
+	    out.print (getSignature (h));
+	    out.print (h.endHtml());		    
+	    out.close();
 	    return;
 	}
+
+	out.println (h.gen (H2, "Individual, acyclic, non-forking paths"));
+	out.println (h.gen (BLOCKQUOTE,
+			    h.gen (FONT, SIZE, "-1",
+				   "Column <em>Workflow</em> contains a workflow definition for the given path. See ") +
+			    h.a (req.getContextPath() + "/Taverna_howto.html", RESULTWIN, "here") +
+			    " details how to use it with " +
+			    h.a ("http://taverna.sf.net", RESULTWIN, "Taverna") + " workflow GUI and engine."));
+	out.println (h.gen (BLOCKQUOTE,
+			    h.gen (FONT, SIZE, "-1",
+				   "Column <em>Workflow Raw</em> contains the same workflow definition but without components for creating Moby input data object, and extracting from Moby output data object. Use this if the service has more complex data type.")));
+
+
+	StringBuffer b = new StringBuffer();
+	for (int i = 0; i < graphURLs.length; i++) {
+	    b.append (h.gen (TR,
+			     h.gen (TD, ALIGN, "right", ""+(i+1)) +
+			     h.gen (TD, ALIGN, "right", ""+nosInGraphs[i]) +
+			     h.gen (TD, ALIGN, "right", ""+noeInGraphs[i]) +
+			     h.gen (TD, h.a (graphURLs[i], RESULTWIN, " Graph ")) +
+			     h.gen (TD, h.a (scuflURLs[i], RESULTWIN, " Workflow ")) +
+			     h.gen (TD, h.a (scuflRawURLs[i], RESULTWIN, " Workflow "))));
+	    b.append ("\n");
+	}
+	out.println
+	    (h.gen (TABLE, new String[] { CELLPADDING, "5", BORDER, "2" },
+		    h.gen (TR,
+			   h.gen (TH, BGCOLOR, COL_HEADER, "&nbsp;") +
+			   h.gen (TH, BGCOLOR, COL_HEADER, "# services") +
+			   h.gen (TH, BGCOLOR, COL_HEADER, "# edges") +
+			   h.gen (TH, BGCOLOR, COL_HEADER, "Graph image") +
+			   h.gen (TH, BGCOLOR, COL_HEADER, "Workflow") +
+			   h.gen (TH, BGCOLOR, COL_HEADER, "Workflow Raw")) +
+		    new String (b)));
+
+	out.println (h.gen (H2, "Joint, but still individual, acyclic, non-forking paths"));
+	out.println (h.gen (BLOCKQUOTE,
+			    h.gen (FONT, SIZE, "-1",
+				   "These graphs are for convenience. They contain the same individual paths as above but collect more of them together.")));
+
+	b = new StringBuffer();
+	for (int i = 0; i < jointGraphURLs.length; i++) {
+	    if (i == jointGraphURLs.length - 1) {
+		b.append (h.gen (TR,
+				 h.gen (TD, (i*pageSize+1) + " - " + (separatePaths.length+1)) +
+				 h.gen (TD, h.a (jointGraphURLs[i], RESULTWIN, " Graph "))));
+	    } else {
+		b.append (h.gen (TR,
+				 h.gen (TD, (i*pageSize+1) + " - " + (i*pageSize+pageSize)) +
+				 h.gen (TD, h.a (jointGraphURLs[i], RESULTWIN, " Graph "))));
+	    }
+	    b.append ("\n");
+	}
+	out.println
+	    (h.gen (TABLE, new String[] { CELLPADDING, "5", BORDER, "2" },
+		    h.gen (TR,
+			   h.gen (TH, BGCOLOR, COL_HEADER, "Paths") +
+			   h.gen (TH, BGCOLOR, COL_HEADER, "Graph image")) +
+		    new String (b)));
+
+ 	out.print (getSignature (h));
+	out.print (h.endHtml());		    
+	out.close();
+    }
+
+    // create a simple data type from (potentiobally empty) namesdpace
+    // 'ns' and a data type
+    static MobyPrimaryDataSimple createSimpleData (String nsName, String dtName) {
+	MobyPrimaryDataSimple data = new MobyPrimaryDataSimple ("dummy_name_for " + dtName);
+	data.setDataType (new MobyDataType (dtName));
+	if (nsName != null && !nsName.equals (WITHOUT_NAMESPACE) && !nsName.equals (""))
+	    data.addNamespace (new MobyNamespace (nsName));
+	return data;
+    }
+
+    // return a number of services involved in 'edges'
+    static int getNumberOfServices (ServicesEdge[] edges) {
+	HashSet services = new HashSet();
+	for (int i = 0; i < edges.length; i++) {
+	    MobyService service = edges[i].getSourceService();
+	    if (service != null)
+		services.add (service.getName());
+	    service = edges[i].getTargetService();
+	    if (service != null)
+		services.add (service.getName());
+	}
+	return services.size();
     }
 
     /*************************************************************************
@@ -732,19 +1312,29 @@
 	if (rankdir != null)
 	    props.put (Graphviz.PROP_RANKDIR, rankdir);
 
+	// this is to distinguish also the cached HTML pages
+	props.put ("wot", wantedOutputType);
+
 	try {
+	    // make sure that we have data from a registry (it is
+	    // either already in the registry cache - so it does not
+	    // take long to read it, or it is not - so it is good to
+	    // refill the cache and to get the new 'lastRead'
+	    // timestamp)
+	    MobyDataType[] dataTypes = registry.getDataTypes();
+	    lastRead = getLastRead();
+
 	    // perhaps we have the same graph in the cache already
-	    props.entrySet().toArray();
-	    String id = cache.createId (endpoint, GRAPH_DATA,
-					wantedOutputType, lastRead, props);
-	    if (! cache.exists (id)) {
-		// create a dot definition of the graph
-		log ("Creating a graph of the data types...\n");
-		createGraph (id, wantedOutputType,
-			     Graphviz.createDataTypesGraph (dataTypes, props));
+	    String graphId = cache.createId (registry.getRegistryEndpoint(), GRAPH_DATA,
+					     wantedOutputType, lastRead, props);
+	    String pageId = cache.createId (registry.getRegistryEndpoint(), GRAPH_DATA,
+					    "html", lastRead, props);
+	    if ( !cache.existsInCache (graphId) || !cache.existsInCache (pageId) ) {
+		// create an HMTL static page (and the graph the page has a link to)
+		doDataTypesResultPage (pageId, graphId, wantedOutputType, req, dataTypes, props);
 	    }
-	    res.sendRedirect (res.encodeRedirectURL (cache.getURL (id)));
-	
+	    res.sendRedirect (res.encodeRedirectURL (cache.getURL (pageId)));
+
 	} catch (MobyException e) {
 	    error (res, res.SC_SERVICE_UNAVAILABLE, e);
 	    return;
@@ -752,6 +1342,61 @@
     }
 
     /*************************************************************************
+     * Create a page containing results for a data types graph.
+     *************************************************************************/
+    protected void doDataTypesResultPage (String pageId, String graphId,
+					  String wantedOutputType,
+					  HttpServletRequest req,
+					  MobyDataType[] dataTypes,
+					  Properties props)
+	throws ServletException, IOException, MobyException {
+
+	// where (and by whom) to create resulting HTML page
+	String filename = cache.getFilename (pageId);
+	PrintWriter out = new PrintWriter (new FileOutputStream (filename));
+	Html h = new Html (req);
+
+	String title = "Data types graph";
+
+	//
+	// create a graph
+	//
+	createGraph (graphId, wantedOutputType,
+		     Graphviz.createDataTypesGraph (dataTypes, props));
+	long fsize = new File (cache.getFilename (graphId)).length();
+
+	//
+	// create the resulting page
+	//
+	out.print (h.startHtml (new String[] {
+	    TITLE, "Moby Graphs Data Types page",
+	    BGCOLOR, BG_COLOR,
+	    LINK, LINK_COLOR, VLINK, VLINK_COLOR, TEXT, TEXT_COLOR }));
+
+	out.println (h.a (cache.getURL (graphId), RESULTWIN, h.gen (H1, title)));
+	out.println (h.gen (BLOCKQUOTE,
+			    h.gen (FONT, SIZE, "-1",
+				   "Click on the header above to get the whole graph. WARNING: Please take image file size into account before downloading! Do not try to open too large image in your browser. Save the file to your computer instead.")));
+
+	out.println
+	    (h.gen (TABLE, new String[] { CELLPADDING, "5" },
+		    h.gen (TR,
+			   h.gen (TD, h.gen (B, "Number of included data types: ")) +
+			   h.gen (TD, ""+dataTypes.length)) +
+		    h.gen (TR,
+			   h.gen (TD, h.gen (B, "Image type: ")) +
+			   h.gen (TD, wantedOutputType)) +
+		    h.gen (TR,
+			   h.gen (TD, h.gen (B, "Image size: ")) +
+			   h.gen (TD, fsize + " B"))
+		    ));
+
+ 	out.print (getSignature (h));
+	out.print (h.endHtml());		    
+	out.close();
+    }
+
+    /*************************************************************************
      * Create and return a graph of Moby service types.
      *************************************************************************/
     protected void doGraphServiceTypes (HttpServletRequest req, HttpServletResponse res)
@@ -782,12 +1427,13 @@
 
 	try {
 	    // perhaps we have the same graph in the cache already
-	    props.entrySet().toArray();
-	    String id = cache.createId (endpoint, GRAPH_SERVT,
+	    String id = cache.createId (registry.getRegistryEndpoint(), GRAPH_SERVT,
 					wantedOutputType, lastRead, props);
-	    if (! cache.exists (id)) {
+	    if (! cache.existsInCache (id)) {
 		// create a dot definition of the graph
 		log ("Creating a graph of the services types...\n");
+		MobyServiceType[] serviceTypes = registry.getFullServiceTypes();
+		lastRead = getLastRead();
 		createGraph (id, wantedOutputType,
 			     Graphviz.createServiceTypesGraph (serviceTypes, props));
 	    }
@@ -816,7 +1462,7 @@
 	// depending on the cache implementation we may ask
 	// 'dot' to produce output to its standard output, or
 	// to write to a file
-	if (cache.supportsFilenames()) {
+// 	if (cache.supportsFilenames()) {
 
 	    // note that this filename represents a not-yet-existing file
 	    String filename = cache.getFilename (id);
@@ -824,25 +1470,38 @@
 	    // call 'dot' to create a real graph in a 'filename'
 	    executeDot (dotProg, graph, wantedOutputType, filename);
 
-	} else {
+// 	} else {
 
-	    // call 'dot' to return a real graph as byte array
-	    byte[] graphBytes = executeDot (dotProg, graph, wantedOutputType);
-	    if (graphBytes == null || graphBytes.length == 0)
-		throw new MobyException ("An empty graph. Strange.");
-	    cache.setContents (id, graphBytes);
-	}
+// 	    // call 'dot' to return a real graph as byte array
+// 	    byte[] graphBytes = executeDot (dotProg, graph, wantedOutputType);
+// 	    if (graphBytes == null || graphBytes.length == 0)
+// 		throw new MobyException ("An empty graph. Strange.");
+// 	    cache.setContents (id, graphBytes);
+// 	}
     }
 
     /********************************************************************
-     *
+     * Return an age of the cache (if known).
+     ********************************************************************/
+    protected long getLastRead() {
+	// note that cache age is supported by CentralDigetsCachedImpl
+	// class but not by the CentralAll interface so we have to
+	// cast it - but let's check first if we have just the right
+	// class for it
+	if (registry instanceof CentralDigestCachedImpl)
+	    return ((CentralDigestCachedImpl)registry).getCacheAge();
+	return -1;
+    }
+
+    /********************************************************************
+     * If the registry cache is too old, it is removed (the method
+     * name is just historical).
      ********************************************************************/
     protected void readRegistryIfNeeded()
 	throws MobyException {
-	if (dataTypes == null || services == null || serviceTypes == null) {
-	    readRegistry();
-	    return;
-	}
+
+	// remember how old is the current cache
+	lastRead = System.currentTimeMillis();
 
 	// do this only the first time
 	if (refreshInterval == -1) {
@@ -857,74 +1516,7 @@
 	}
 
 	if (System.currentTimeMillis() - lastRead > refreshInterval)
-	    readRegistry();
-    }
-
-    /********************************************************************
-     *
-     ********************************************************************/
-    protected synchronized void readRegistry()
-	throws MobyException {
-
-	// do this only the first time
-	if (registry == null) {
-	    registry = new CentralImpl (endpoint, namespace);
-	    ((CentralImpl)registry).setCacheMode (false);
-	    registry.setDebug (debug);
-	}
-
-	try {
-
-	    // read all data types and their relationships
-	    Vector v = new Vector();
-	    log ("Asking for all data type names...\n");
-	    Map types = registry.getDataTypeNames();
-	    for (Iterator it = types.entrySet().iterator(); it.hasNext(); ) {
-		Map.Entry entry = (Map.Entry)it.next();
-		String name = (String)entry.getKey();
-		log ("Processing " + name + "...\n");
-		v.addElement (registry.getDataType (name));
-	    }
-	    dataTypes = new MobyDataType [v.size()];
-	    v.copyInto (dataTypes);
-
-	    // read all services types and their relationships
-	    v.clear();
-	    log ("Asking for all service types...\n");
-	    types = registry.getServiceTypes();
-	    for (Iterator it = types.entrySet().iterator(); it.hasNext(); ) {
-		Map.Entry entry = (Map.Entry)it.next();
-		String typeName = (String)entry.getKey();
-		log ("Processing service type " + typeName + "...\n");
-		MobyServiceType serviceType = new MobyServiceType (typeName);
-		serviceType.setDescription ((String)entry.getKey());
-		serviceType.setParentNames (registry.getServiceTypeRelationships (typeName, false));
-		v.addElement (serviceType);
-	    }
-	    serviceTypes = new MobyServiceType [v.size()];
-	    v.copyInto (serviceTypes);
-
-	    // read all services
-	    v.clear();
-	    log ("Asking for all service names...\n");
-	    Map names = registry.getServiceNames();
-	    for (Iterator it = names.entrySet().iterator(); it.hasNext(); ) {
-		Map.Entry entry = (Map.Entry)it.next();
-		String name = (String)entry.getKey();
-		log ("Processing service " + name + "...\n");
-		MobyService[] servs = registry.findService (new MobyService (name));
-		for (int i = 0; i < servs.length; i++)
-		    v.addElement (servs[i]);
-	    }
-	    services = new MobyService [v.size()];
-	    v.copyInto (services);
-
-	    // remember when we did this
-	    lastRead = System.currentTimeMillis();
-
-	} catch (NoSuccessException e) {
-	    throw new MobyException (e.getMessage() + " in " + e.getCulprit().toString());
-	}
+	    ((CentralDigestImpl)registry).removeFromCache (null);
     }
 
     /********************************************************************
@@ -1072,8 +1664,8 @@
      *************************************************************************/
     protected String getFieldsAsHidden (Html h) {
 	StringBuffer buf = new StringBuffer();
-	buf.append (h.hidden (REGISTRY_URL, endpoint, true));   buf.append ("\n");
-	buf.append (h.hidden (REGISTRY_URI, namespace, true));  buf.append ("\n");
+	buf.append (h.hidden (REGISTRY_URL, registry.getRegistryEndpoint(), true));   buf.append ("\n");
+	buf.append (h.hidden (REGISTRY_URI, registry.getRegistryNamespace(), true));  buf.append ("\n");
 	if (verbose) {
 	    buf.append (h.hidden (VERBOSE, "1", true));
 	    buf.append ("\n");
@@ -1123,17 +1715,17 @@
     }
 
     /********************************************************************
-     * Initialize cache (for created graphs). Separated here for
+     * Initialize cache (for created graphs). It is separated here for
      * inheriting classes which may wish to use a different cache
      * implementation.
      *
      * All caches instantiated by this method are filesystem-based
      * caches.  If there is an init parameter CACHE_DIR we store
-     * cached files starting from this directory, otherwise we store
-     * them inside this servlet context on the 'contextPath'.
+     * cached files starting from CACHE_DIR directory, otherwise we
+     * store them inside this servlet context on the 'contextPath'.
      ********************************************************************/
-    protected SimpleCache initCache (ServletContext context,
-				     String contextPath) {
+    protected SimpleFileCache initCache (ServletContext context,
+					 String contextPath) {
 	String cacheDir = (String)initParams.get (CACHE_DIR);
 	if (UUtils.isEmpty (cacheDir)) {
 	    return new ServletFileCache (context, contextPath);

===================================================================
RCS file: /home/repository/moby/moby-live/Java/src/main/org/biomoby/client/Graphviz.java,v
retrieving revision 1.4
retrieving revision 1.5
diff -u -r1.4 -r1.5
--- /home/repository/moby/moby-live/Java/src/main/org/biomoby/client/Graphviz.java	2004/09/24 19:52:56	1.4
+++ /home/repository/moby/moby-live/Java/src/main/org/biomoby/client/Graphviz.java	2004/10/18 14:35:06	1.5
@@ -8,8 +8,10 @@
 
 import java.util.Enumeration;
 import java.util.Hashtable;
+import java.util.HashSet;
 import java.util.Properties;
 
+import org.biomoby.shared.MobyService;
 import org.biomoby.shared.MobyDataType;
 import org.biomoby.shared.MobyRelationship;
 import org.biomoby.shared.MobyServiceType;
@@ -37,10 +39,14 @@
 
     /*************************************************************************
      * Creates a graph connecting Moby services as defined in a set of
-     * the graph 'edges.  <p>
+     * the graph 'edges'.  <p>
      *
      * @param edges represent services and their connectors in the
-     * created graph
+     * created graph; some edges may be of type {@link
+     * DataServiceEdge} (which is a subclass of ServicesEdge) - those
+     * represent a special type of connection betweern a service and
+     * an input or ouput data type
+     *
      * @param props are some properties that can influence how the
      * graph will look like; see the property names elswhere in this
      * API what properties are understood
@@ -48,7 +54,7 @@
      * @return a string with all definitions as understood by 'dot'
      * program (from the graphviz package); this string can be saved
      * in a '.dot' file that can be passed to a dot program to produce
-     * graphs in many available formats
+     * graphs in many available image formats
      *
      *************************************************************************/
     static public String createServicesGraph (ServicesEdge[] edges,
@@ -57,52 +63,178 @@
 	StringBuffer buf = new StringBuffer();
 	buf.append ("digraph MobyServices {\n");
 	buf.append ("\trankdir=" + props.getProperty (PROP_RANKDIR, "LR") + ";\n");
+	buf.append (createBodyServicesGraph (edges, 0));
+	buf.append ("}\n");
+	return new String (buf);
+    }
+
+    /*************************************************************************
+     * Creates a graph connecting Moby services as defined in a set of
+     * the graph 'paths'. The resulting graph will include only paths
+     * (as separate clusters) starting from paths[fromPath] to
+     * paths[toPath].
+     *
+     * @param paths is an array of edges; each set of edges defines
+     * one path; some edges may be of type {@link DataServiceEdge}
+     * (which is a subclass of ServicesEdge) - those represent a
+     * special type of connection betweern a service and an input or
+     * ouput data type
+     *
+     * @param fromPath a starting index in array 'paths'
+     *
+     * @param toPath an ending index in array 'paths'
+     *
+     * @param pathNames gives names of 'paths' (it may be used to
+     * label individual graph clusters); the array should have the
+     * same dimension as 'path' - and their elements should correspond
+     * to each other
+     *
+     * @param props are some properties that can influence how the
+     * graph will look like; see the property names elswhere in this
+     * API what properties are understood
+     *
+     * @return a string with all definitions as understood by 'dot'
+     * program (from the graphviz package); this string can be saved
+     * in a '.dot' file that can be passed to a dot program to produce
+     * graphs in many available image formats
+     *
+     *************************************************************************/
+    static public String createServicesGraph (ServicesEdge[][] paths, int fromPath, int toPath,
+					      String[] pathNames,
+					      Properties props) {
 
+	StringBuffer buf = new StringBuffer();
+	buf.append ("digraph MobyServices {\n");
+	buf.append ("\trankdir=" + props.getProperty (PROP_RANKDIR, "LR") + ";\n");
+	for (int i = fromPath; i <= toPath; i++) {
+	    buf.append ("subgraph " + i + "{\n");
+	    buf.append (createBodyServicesGraph (paths[i], (i+1)));
+	    buf.append ("}\n");
+	}
+	buf.append ("}\n");
+	return new String (buf);
+    }
+
+
+    // do the main job - only the graph itself, no surrounding
+    // brackets etc; separated here so more graphs can be created and
+    // then put together as subgraphs; that's why it identifies graph
+    // nodes by given number (so other subgraph may use the same nodes
+    // but without beign connected between subgraphs)
+    static String createBodyServicesGraph (ServicesEdge[] edges,
+					   int graphId) {
+
+	HashSet nodes = new HashSet();   // to prevent creating nodes several times
+	StringBuffer buf = new StringBuffer();
 	for (int i = 0; i < edges.length; i++) {
 	    ServicesEdge edge = edges[i];
-	    if (edge.targetService == null) {
-		buf.append ("\t" + edge.sourceService.getName() + "\n");
-	    } else {
-		StringBuffer edgeAttrs = new StringBuffer();
-		if (edge.isWeakConnection()) {
-		    appendAfterComma (edgeAttrs, "style=dotted");
-		}
-		switch (edge.getConnectionType()) {
-		case ServicesEdge.SIMPLE_CONNECTION:
-		    appendAfterComma (edgeAttrs, "arrowtail=none");
-		    appendAfterComma (edgeAttrs, "arrowhead=open");
-		    break;
-		case ServicesEdge.HEAD_COLLECTION_CONNECTION:
-		    appendAfterComma (edgeAttrs, "arrowtail=inv");
-		    appendAfterComma (edgeAttrs, "arrowhead=open");
-		    break;
-		case ServicesEdge.TAIL_COLLECTION_CONNECTION:
-		    appendAfterComma (edgeAttrs, "arrowtail=none");
-		    appendAfterComma (edgeAttrs, "arrowhead=normal");
-		    break;
-		case ServicesEdge.BOTH_COLLECTIONS_CONNECTION:
-		    appendAfterComma (edgeAttrs, "arrowtail=inv");
-		    appendAfterComma (edgeAttrs, "arrowhead=normal");
-		    break;
+	    StringBuffer edgeAttrs = edgeAttributes (edge);
+
+	    if (edge instanceof DataServiceEdge) {
+		DataServiceEdge dedge = (DataServiceEdge)edge;
+		String dataTypeName = Utils.pureName (dedge.getDataType().getName());
+		if (! nodes.contains (dataTypeName)) {
+		    buf.append ("\t" +
+				quoteIt (dataTypeName + "_" + graphId) +
+				new String (dataTypeAttributes (dataTypeName)) +
+				"\n");
+		    nodes.add (dataTypeName);
 		}
-		String connector = edge.getConnector();
-		if (! connector.equals (""))
-		    appendAfterComma (edgeAttrs, "label=\"" + connector + "\"");
-		if (edgeAttrs.length() > 0) {
-		    edgeAttrs.insert (0, " [");
-		    edgeAttrs.append ("]");
+		buf.append ("\t" +
+			    new String (serviceNode (dedge.getService(), nodes, graphId)) +
+			    "\n");
+		if (dedge.isEndingEdge()) {
+		    buf.append ("\t" +
+				quoteIt (dedge.getService().getName() + "_" + graphId) + " -> " +
+				quoteIt (dataTypeName + "_" + graphId) +
+				new String (edgeAttrs) + "\n");
+		} else {
+		    buf.append ("\t" +
+				quoteIt (dataTypeName + "_" + graphId) + " -> " +
+				quoteIt (dedge.getService().getName() + "_" + graphId) +
+ 				new String (edgeAttrs) + "\n");
 		}
+
+	    } else {
 		buf.append ("\t" +
-			    quoteIt (edge.sourceService.getName()) + " -> " +
-			    quoteIt (edge.targetService.getName()) +
-			    new String (edgeAttrs) + "\n");
+			    new String (serviceNode (edge.sourceService, nodes, graphId)) +
+			    "\n");
+		buf.append ("\t" +
+			    new String (serviceNode (edge.targetService, nodes, graphId)) +
+			    "\n");
+		if (edge.sourceService != null && edge.targetService != null) {
+		    buf.append ("\t" +
+				quoteIt (edge.sourceService.getName() + "_" + graphId) + " -> " +
+				quoteIt (edge.targetService.getName() + "_" + graphId) +
+				new String (edgeAttrs) + "\n");
+		}
 	    }
 	}
-
-	buf.append ("}\n");
 	return new String (buf);
     }
 
+    // a service node
+    static StringBuffer serviceNode (MobyService service, HashSet nodes, int graphId) {
+	StringBuffer buf = new StringBuffer();
+	if (service == null)
+	    return buf;
+	String srvName = service.getName();
+	if (! nodes.contains (srvName)) {
+	    buf.append (quoteIt (srvName + "_" + graphId));
+	    buf.append (" [label=\"");
+	    buf.append (srvName);
+	    buf.append ("\"]");
+	    nodes.add (srvName);
+	}
+	return buf;
+    }
+
+    //
+    static StringBuffer edgeAttributes (ServicesEdge edge) {
+	StringBuffer edgeAttrs = new StringBuffer();
+	if (edge.isWeakConnection()) {
+	    appendAfterComma (edgeAttrs, "style=dotted");
+	}
+	switch (edge.getConnectionType()) {
+	case ServicesEdge.SIMPLE_CONNECTION:
+	    appendAfterComma (edgeAttrs, "arrowtail=none");
+	    appendAfterComma (edgeAttrs, "arrowhead=open");
+	    break;
+	case ServicesEdge.HEAD_COLLECTION_CONNECTION:
+	    appendAfterComma (edgeAttrs, "arrowtail=inv");
+	    appendAfterComma (edgeAttrs, "arrowhead=open");
+	    break;
+	case ServicesEdge.TAIL_COLLECTION_CONNECTION:
+	    appendAfterComma (edgeAttrs, "arrowtail=none");
+	    appendAfterComma (edgeAttrs, "arrowhead=normal");
+	    break;
+	case ServicesEdge.BOTH_COLLECTIONS_CONNECTION:
+	    appendAfterComma (edgeAttrs, "arrowtail=inv");
+	    appendAfterComma (edgeAttrs, "arrowhead=normal");
+	    break;
+	}
+	String connector = edge.getConnector();
+	if (! connector.equals (""))
+	    appendAfterComma (edgeAttrs, "label=\"" + connector + "\"");
+	if (edgeAttrs.length() > 0) {
+	    edgeAttrs.insert (0, " [");
+	    edgeAttrs.append ("]");
+	}
+	return edgeAttrs;
+    }
+
+    //
+    static StringBuffer dataTypeAttributes (String nodeLabel) {
+	StringBuffer attrs = new StringBuffer();
+	appendAfterComma (attrs, "label=\"" + nodeLabel + "\"");
+	appendAfterComma (attrs, "shape=\"box\"");
+	appendAfterComma (attrs, "fillcolor=\"skyblue\"");
+	appendAfterComma (attrs, "style=\"filled\"");
+	attrs.insert (0, " [");
+	attrs.append ("]");
+	return attrs;
+    }
+
     /*************************************************************************
      * Creates a graph connecting 'dataTypes' using their ISA
      * relationship and showing also their HASA children.

===================================================================
RCS file: /home/repository/moby/moby-live/Java/src/main/org/biomoby/client/ServiceConnections.java,v
retrieving revision 1.2
retrieving revision 1.3
diff -u -r1.2 -r1.3
--- /home/repository/moby/moby-live/Java/src/main/org/biomoby/client/ServiceConnections.java	2004/04/01 16:41:03	1.2
+++ /home/repository/moby/moby-live/Java/src/main/org/biomoby/client/ServiceConnections.java	2004/10/18 14:35:06	1.3
@@ -31,6 +31,149 @@
 public abstract class ServiceConnections {
 
 
+
+    // make the data types better searchable
+    // (this is public - so it can be done only once)
+    public static Hashtable optimizedDataTypes (MobyDataType[] dataTypes) {
+	Hashtable dataTypesTable = new Hashtable();
+	for (int i = 0; i < dataTypes.length; i++) {
+	    MobyDataType dataType = dataTypes[i];
+	    dataTypesTable.put (Utils.pureName (dataType.getName()).toLowerCase(), dataType);
+	}
+	return dataTypesTable;
+    }
+
+    //
+    public static DataServiceEdge[] findStartingEdges (MobyPrimaryDataSimple sourceData,
+						       MobyDataType[] dataTypes,
+						       MobyService[] services) {
+
+	// make the data types better searchable
+	Hashtable dataTypesTable = optimizedDataTypes (dataTypes);
+
+	// here we are going to build the resulting edges
+	Vector v = new Vector();
+
+	String sourceDataTypeName = sourceData.getDataType().getName();
+	MobyDataType sourceDataType =
+	    (MobyDataType)dataTypesTable.get (Utils.pureName (sourceDataTypeName).toLowerCase());
+	if (sourceDataType == null)
+	    return new DataServiceEdge[] {};
+
+	// find services having this type as an input
+	for (int t = 0; t < services.length; t++) {
+	    MobyService targetService = services[t];
+	    MobyData[] serviceInputs = targetService.getPrimaryInputs();
+	    // service without any inputs
+	    if (serviceInputs == null || serviceInputs.length == 0)
+		continue;
+
+	    boolean tailCollection = false;
+	    MobyPrimaryDataSimple serviceInput;
+	    for (int j = 0; j < serviceInputs.length; j++) {
+		if (serviceInputs[j] instanceof MobyPrimaryDataSet) {
+		    MobyPrimaryDataSet collection = (MobyPrimaryDataSet)serviceInputs[j];
+		    MobyPrimaryDataSimple[] elements = collection.getElements();
+		    if (elements.length == 0)
+			continue;   // ignoring empty collections
+		    // I assume that all elements are of the same type
+		    // - (which is not generally true :-(   TBD )
+		    serviceInput = elements[0];
+		    tailCollection = true;
+		} else if (serviceInputs[j] instanceof MobyPrimaryDataSimple) {
+		    serviceInput = (MobyPrimaryDataSimple)serviceInputs[j];
+		} else {
+		    // it is a programming bug if this happens
+		    System.err.println ("Service " + targetService.getName() +
+					": Input is represented by an unknown Java type " +
+					serviceInputs[j].getClass().getName());
+		    continue;
+		}
+
+		DataTypeConnector connector = compareTypes (sourceData, serviceInput, dataTypesTable);
+		if (connector != null) {
+		    DataServiceEdge edge = new DataServiceEdge (sourceDataType, targetService, connector.toString());
+		    edge.setConnectionType (tailCollection ?
+					    ServicesEdge.TAIL_COLLECTION_CONNECTION :
+					    ServicesEdge.SIMPLE_CONNECTION);
+		    v.addElement (edge);
+		}
+	    }
+	}
+
+	// pack and return all resulting edges
+	DataServiceEdge[] results = new DataServiceEdge [v.size()];
+	v.copyInto (results);
+	return results;
+    }
+
+
+    //
+    public static DataServiceEdge[] findEndingEdges (MobyPrimaryDataSimple targetData,
+						     MobyDataType[] dataTypes,
+						     MobyService[] services) {
+
+	// make the data types better searchable
+	Hashtable dataTypesTable = optimizedDataTypes (dataTypes);
+
+	// here we are going to build the resulting edges
+	Vector v = new Vector();
+
+	String targetDataTypeName = targetData.getDataType().getName();
+	MobyDataType targetDataType =
+	    (MobyDataType)dataTypesTable.get (Utils.pureName (targetDataTypeName).toLowerCase());
+	if (targetDataType == null)
+	    return new DataServiceEdge[] {};
+
+	// find services having this type as an output
+	for (int s = 0; s < services.length; s++) {
+	    MobyService sourceService = services[s];
+	    MobyData[] serviceOutputs = sourceService.getPrimaryOutputs();
+	    
+	    // service without any output (any sense?)
+	    if (serviceOutputs == null || serviceOutputs.length == 0)
+		continue;
+
+	    boolean headCollection = false;
+	    MobyPrimaryDataSimple serviceOutput;
+	    for (int j = 0; j < serviceOutputs.length; j++) {
+		if (serviceOutputs[j] instanceof MobyPrimaryDataSet) {
+		    MobyPrimaryDataSet collection = (MobyPrimaryDataSet)serviceOutputs[j];
+		    MobyPrimaryDataSimple[] elements = collection.getElements();
+		    if (elements.length == 0)
+			continue;   // ignoring empty collections
+		    // I assume that all elements are of the same type
+		    // - (which is not generally true :-(   TBD )
+		    serviceOutput = elements[0];
+		    headCollection = true;
+		} else if (serviceOutputs[j] instanceof MobyPrimaryDataSimple) {
+		    serviceOutput = (MobyPrimaryDataSimple)serviceOutputs[j];
+		} else {
+		    // it is a programming bug if this happens
+		    System.err.println ("Service " + sourceService.getName() +
+					": Output is represented by an unknown Java type " +
+					serviceOutputs[j].getClass().getName());
+		    continue;
+		}
+
+		DataTypeConnector connector = compareTypes (serviceOutput, targetData, dataTypesTable);
+		if (connector != null) {
+		    DataServiceEdge edge = new DataServiceEdge (sourceService, targetDataType, connector.toString());
+		    edge.setConnectionType (headCollection ?
+					    ServicesEdge.HEAD_COLLECTION_CONNECTION :
+					    ServicesEdge.SIMPLE_CONNECTION);
+		    v.addElement (edge);
+		}
+	    }
+	}
+
+	// pack and return all resulting edges
+	DataServiceEdge[] results = new DataServiceEdge [v.size()];
+	v.copyInto (results);
+	return results;
+    }
+
+
     /*************************************************************************
      * Creates all (allowed) connections between given 'services'
      * based on the given 'dataTypes'. The returned array of found
@@ -66,11 +209,7 @@
 					MobyService[] services) {
 
 	// make the data types better searchable
-	Hashtable dataTypesTable = new Hashtable();
-	for (int i = 0; i < dataTypes.length; i++) {
-	    MobyDataType dataType = dataTypes[i];
-	    dataTypesTable.put (Utils.pureName (dataType.getName()).toLowerCase(), dataType);
-	}
+	Hashtable dataTypesTable = optimizedDataTypes (dataTypes);
 
 	// here we are going to build the resulting edges
 	Vector v = new Vector();
@@ -154,9 +293,9 @@
 			    continue;
 			}
 
-			String connector = compareTypes (output, input, dataTypesTable);
+			DataTypeConnector connector = compareTypes (output, input, dataTypesTable);
 			if (connector != null) {
-			    ServicesEdge edge = new ServicesEdge (sourceService, targetService, connector);
+			    ServicesEdge edge = new ServicesEdge (sourceService, targetService, connector.toString());
 			    if (headCollection && tailCollection)
 				edge.setConnectionType (ServicesEdge.BOTH_COLLECTIONS_CONNECTION);
 			    else if (headCollection)
@@ -200,9 +339,12 @@
      *    virtualsequence
      *    object
      *************************************************************************/
-    static String compareTypes (MobyPrimaryDataSimple output,
-				MobyPrimaryDataSimple input,
-				Hashtable dataTypes) {
+    static DataTypeConnector compareTypes (MobyPrimaryDataSimple output,
+					   MobyPrimaryDataSimple input,
+					   Hashtable dataTypes) {
+
+	DataTypeConnector connector = new DataTypeConnector();
+	connector.inputDataType = input.getDataType();
 
 	// first try to find if there is a matching namespace between
 	// 'output' and 'input' (a matching is found also when there
@@ -232,13 +374,12 @@
 	}
 
 	// now try to find if input and output data types match: first
-	// try s direct match between output and input types, then use
+	// try a direct match between output and input types, then use
 	// the inheritance lader and match when the input type is a
 	// parent of the output type (in other words a service should
 	// be able to accept data that are more specialized than the
 	// service claim or can use)
 
-	String connector = null;
 	MobyDataType outputType =
 	    (MobyDataType)dataTypes.get (Utils.pureName (output.getDataType().getName()).toLowerCase());
 	if (outputType == null) // strange...
@@ -247,20 +388,17 @@
 	String inputName = Utils.pureName (input.getDataType().getName());
 
 	if (outputName.equals (inputName))
-	    connector = outputName;
+	    connector.outputDataType = outputType;
 	else if (findMatchInParents (outputType.getParentNames(), inputName, dataTypes))
-	    connector = outputName;
+	    connector.outputDataType = outputType;
 	else
 	    return null;
 
 	// now we have a connector, let's add there there namespace (if any)
-	if (matchingNamespace == null)
-	    return Utils.pureName (connector);
-	else
-	    return
-		Utils.pureName (matchingNamespace) +
-		ServicesEdge.NS_DIVIDER +      // something like '/'
-		Utils.pureName (connector);
+	if (matchingNamespace != null)
+	    connector.matchingNamespaceName = matchingNamespace;
+
+	return connector;
     }
 
     /*************************************************************************

===================================================================
RCS file: /home/repository/moby/moby-live/Java/src/main/org/biomoby/client/ServicesEdge.java,v
retrieving revision 1.2
retrieving revision 1.3
diff -u -r1.2 -r1.3
--- /home/repository/moby/moby-live/Java/src/main/org/biomoby/client/ServicesEdge.java	2003/11/08 00:27:24	1.2
+++ /home/repository/moby/moby-live/Java/src/main/org/biomoby/client/ServicesEdge.java	2004/10/18 14:35:06	1.3
@@ -53,6 +53,35 @@
     int connectionType = NO_CONNECTION;
     boolean isWeakConnection = false;
 
+    public String getId() {
+	StringBuffer buf = new StringBuffer();
+	buf.append (sourceService);
+	buf.append ("|");
+	buf.append (targetService);
+	buf.append ("|");
+	buf.append (connector);
+	buf.append ("|");
+	buf.append ("" + connectionType);
+	buf.append ("|");
+	buf.append ("" + isWeakConnection);
+	return new String (buf);
+    }
+
+    public String toString() {
+	StringBuffer buf = new StringBuffer();
+	buf.append ( (sourceService == null ? "null" : sourceService.getName()) );
+	buf.append (" ---( ");
+	buf.append (connector);
+	buf.append (" )--> ");
+	buf.append ( (targetService == null ? "null" : targetService.getName()) );
+	return new String (buf);
+    }
+
+    public String extractNamespace() {
+	int pos = connector.indexOf (NS_DIVIDER);
+	return (pos > -1 ? connector.substring (0, pos) : "");
+    }
+
     /*************************************************************************
      * Constructs an instance with a source service and a connection
      * type.  It prints a warning on the STDERR if the connection type

===================================================================
RCS file: /home/repository/moby/moby-live/Java/src/main/org/biomoby/client/ServletFileCache.java,v
retrieving revision 1.2
retrieving revision 1.3
diff -u -r1.2 -r1.3
--- /home/repository/moby/moby-live/Java/src/main/org/biomoby/client/ServletFileCache.java	2004/04/01 21:03:42	1.2
+++ /home/repository/moby/moby-live/Java/src/main/org/biomoby/client/ServletFileCache.java	2004/10/18 14:35:06	1.3
@@ -15,7 +15,7 @@
 
 /**
  * A simple cache implementation meant to be used in a servlet
- * environments in order to save and later return (any) data, or to
+ * environment in order to save and later return (any) data, or to
  * find that the given data are not available in the cache. It uses
  * files to store data. <p>
  *

===================================================================
RCS file: /home/repository/moby/moby-live/Java/src/main/org/biomoby/client/SimpleCache.java,v
retrieving revision 1.1
retrieving revision 1.2
diff -u -r1.1 -r1.2
--- /home/repository/moby/moby-live/Java/src/main/org/biomoby/client/SimpleCache.java	2003/11/08 00:27:24	1.1
+++ /home/repository/moby/moby-live/Java/src/main/org/biomoby/client/SimpleCache.java	2004/10/18 14:35:06	1.2
@@ -14,8 +14,7 @@
 
 
 /**
- * An interface defining basic operation for caching data, and
- * providing access to them via URLs.
+ * An interface defining basic operation for caching data.
  * <p>
  *
  * @author <A HREF="mailto:senger at ebi.ac.uk">Martin Senger</A>
@@ -58,60 +57,30 @@
      * @return true if the object identified by 'id' already exists in
      * this cache
      **************************************************************************/
-    boolean exists (String id);
+    boolean existsInCache (String id);
 
     /**************************************************************************
-     * Does this cache support storing objects in a file system?
-     * Returning TRUE indicates that the {@link #getFilename} can be
-     * used. Having a cache supporting objects as files may be
-     * efficient if objects are later also used as files (e.g. if they
-     * are used by external processes).  <p>
+     * Return 'data' identified by 'id'.
      *
-     * Why to have this method at all, why not to use directly
-     * {@link #getFilename} returning null? That's because 
-     * <p>
-     *
-     * @return true if this cache can return cached objects as files
+     * @param id a unique ID of the object being returned
+     * @return data previously stored under 'id'; or null if such data
+     * do not exist
+     * @thow IOExcepiton if the retrieving failed
      **************************************************************************/
-    boolean supportsFilenames();
-
-    /**************************************************************************
-     * Return a full path to a file that represents the cached object
-     * identified by its 'id'. Note that this file does not need to
-     * exist yet (if no object with given 'id' was stored) - but the
-     * path to the file must exist (or must be created by this
-     * method). This will allow to use the returned filename in other
-     * classes or even external processes in order to create them
-     * (actually using them to store data in this cache).  <p>
-     *
-     * @param id a unique ID of the cached (or possibly cached) object
-     * @return a filename with the full path representing an object 'id'
-     * @throw IOException if creating the parent directories caused problem
-     * @see #supportsFilenames
-     **************************************************************************/
-    String getFilename (String id)
+    java.lang.Object getContents (String id)
 	throws IOException;
 
     /**************************************************************************
-     * Store 'data' as an object identified by 'id'.
+     * Store/cache 'data' identified by 'id'.
      *
      * @param id a unique ID of the object being stored
      * @param data are being stored
      * @thow IOExcepiton if the storing failed
      **************************************************************************/
-    void setContents (String id, byte[] data)
+    void setContents (String id, java.lang.Object data)
 	throws IOException;
 
     /**************************************************************************
-     * Return a full URL allowing to retrieve a cached object
-     * identified by its 'id'.
-     * <p>
-     *
-     * @param id a unique ID 
-     **************************************************************************/
-    public String getURL (String id);
-
-    /**************************************************************************
      * Remove cached object identified by its 'id' from the cache.
      * <p>
      *
@@ -120,19 +89,7 @@
      * (meaning that it <b>does not</b> raise any exception if the
      * object is not anymore in the cache)
      **************************************************************************/
-    void remove (String id)
-	throws IOException;
-
-    /**************************************************************************
-     * Remove all cached objects that are in cache longer that
-     * specifies by 'millis'.  <p>
-     *
-     * @param millis how many milliseconds must be an object stored to
-     * be remopved by calling this method
-     * @throw IOException if any object that exists in the cache and
-     * is targeted to be removed but cannot be removed
-     **************************************************************************/
-    void removeOlderThen (long millis)
+    void removeFromCache (String id)
 	throws IOException;
 
 }




More information about the MOBY-guts mailing list