Index: trunk/gsdl3/src/java/org/greenstone/gsdl3/service/ServiceRack.java
===================================================================
--- trunk/gsdl3/src/java/org/greenstone/gsdl3/service/ServiceRack.java	(revision 3650)
+++ trunk/gsdl3/src/java/org/greenstone/gsdl3/service/ServiceRack.java	(revision 3650)
@@ -0,0 +1,277 @@
+/*
+ *    ServiceRack.java
+ *    Copyright (C) 2002 New Zealand Digital Library, http://www.nzdl.org
+ *
+ *    This program is free software; you can redistribute it and/or modify
+ *    it under the terms of the GNU General Public License as published by
+ *    the Free Software Foundation; either version 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program is distributed in the hope that it will be useful,
+ *    but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *    GNU General Public License for more details.
+ *
+ *    You should have received a copy of the GNU General Public License
+ *    along with this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+package org.greenstone.gsdl3.service;
+
+// greenstone classes
+import org.greenstone.gsdl3.util.*;
+import org.greenstone.gsdl3.core.*;
+
+// xml classes
+import org.w3c.dom.Node; 
+import org.w3c.dom.NodeList; 
+import org.w3c.dom.Element; 
+import org.w3c.dom.Document; 
+import org.xml.sax.InputSource;
+import javax.xml.parsers.*;
+import org.apache.xpath.XPathAPI;
+
+// general java classes
+import java.io.Reader;
+import java.io.StringReader;
+import java.io.File;
+import java.util.HashMap;
+import java.util.ResourceBundle;
+import java.util.Locale;
+import java.lang.reflect.Method;
+
+/**
+ * ServiceRack - abstract base class for services
+ *
+ * A ServiceRack provides one or more Services. 
+ * This base class implements the process method.
+ *Each service is invoked
+ * by a method called process<service name> which takes one parameter - the xml request Element, and returns an XML response Element. 
+ * for example, the TextQuery service would be invoked by 
+ * processTextQuery(Element request)
+ *
+ * @author <a href="mailto:kjdon@cs.waikato.ac.nz">Katherine Don</a>
+ * @version $Revision$
+ */
+public abstract class ServiceRack
+    implements ModuleInterface
+{
+
+    /** the absolute address of the site home */
+    protected String site_home_ =null;
+    /** the name of the cluster (or collection) that this service 
+	belongs to - if any */ 
+    protected String cluster_name_ = null;
+
+    /** some services can talk back to the message router */
+    protected ModuleInterface router_ = null;
+
+    /** a converter class to create Documents etc */
+    protected XMLConverter converter_ = null;
+
+    /** XML element for describe requests - the container doc */
+    protected Document doc_ = null;
+
+    /** XML element for describe requests - list of supported services */
+    protected Element short_service_info_ = null;
+
+    /** XML element for describe requests - map of service name to full 
+	description */
+    protected HashMap service_info_map_ = null;
+
+    /** sets the cluster name */
+    public void setClusterName(String cluster_name) {
+	cluster_name_ = cluster_name;
+    }
+    /** sets the collect name */
+    public void setCollectionName(String coll_name) {
+	setClusterName(coll_name);
+    }
+    
+    /** sets the site home */
+    public void setSiteHome(String site_home) {
+	site_home_ = site_home;
+    }
+
+    /** sets the message router */
+    public void setMessageRouter(ModuleInterface m) {
+       router_ = m;
+    }
+
+    /** the no-args constructor */
+    public ServiceRack() {
+	converter_ = new XMLConverter();
+	doc_ = converter_.newDOM();
+	short_service_info_ = doc_.createElement(GSXML.SERVICE_ELEM+GSXML.LIST_MODIFIER);
+	service_info_map_ = new HashMap();
+    }
+    
+
+    /** configure the service module
+     *
+     * @param info the XML node <serviceRack name="XXX"/> with name equal
+     * to the class name (of the subclass)
+     *
+     * must configure short_service_info_ and service_info_map_
+     * @return true if configured ok
+     * must be implemented in subclasses
+     */
+    abstract public boolean configure(Element info); 
+
+    /**
+     * Process an XML document - convenience method that uses Strings rather than Elements. just calls process(Element).
+     *
+     * @param xml_in the Document to process - a string
+     * @return the resultant document as a string - contains any error messages
+     * @see String
+     */
+    public String process(String xml_in) {
+	
+	Document doc = converter_.getDOM(xml_in);
+	
+	Element res = process(doc.getDocumentElement());
+	return converter_.getString(res);
+	
+    }
+
+    /** process an XML request in DOM form
+     *
+     * @param xml_in the Element node containing the request
+     * should be <message> 
+     * @return an Element with the result XML
+     * @see Element
+     */
+    public Element process(Element message) {
+
+	NodeList requests = message.getElementsByTagName(GSXML.REQUEST_ELEM);
+	Document mess_doc = message.getOwnerDocument();
+	Element mainResult = doc_.createElement(GSXML.MESSAGE_ELEM);
+	if (requests.getLength()==0) {
+	    // no requests
+	    return mainResult; // empty message for now
+	}
+
+	for (int i=0; i<requests.getLength(); i++) {
+	    Element request = (Element)requests.item(i);
+	
+	    String type = request.getAttribute(GSXML.TYPE_ATT);
+	    if (type.equals(GSXML.REQUEST_TYPE_DESCRIBE)) {
+		Element response = processDescribe(request);
+		if (response !=null) {
+		    mainResult.appendChild(doc_.importNode(response, true));
+		}
+		
+	    } else {
+		// other type of request, must be processed by the subclass - 
+		// send to the service method
+
+		String to = GSPath.getFirstLink(request.getAttribute(GSXML.TO_ATT));		
+		Element response = null;
+		if (service_info_map_.containsKey(to)) { 
+		    
+		    try {
+			Class c = this.getClass();
+			Class []params = {Class.forName("org.w3c.dom.Element")};
+			Method m = c.getDeclaredMethod("process"+to, params);
+			Object []args = {request};
+			response = (Element)m.invoke(this, args);
+			
+		    } catch (Exception e) {
+			System.out.println("ServiceRack: Trying to call a processService type method on a subclass, but an exception happened:"+e.toString());
+			return null;
+		    }
+		    if (response !=null) {
+			mainResult.appendChild(doc_.importNode(response, true));
+		    }
+		} else {
+		    // else error in to field
+		    System.err.println("ServiceRack describe request: error in 'to' field, non-existant service "+to+" specified!");
+		    return null;
+		}
+	    }
+	} // for each request
+
+	return mainResult;
+		
+    }
+
+    /** process method for describe requests 
+     */
+    protected Element processDescribe(Element request) {
+	
+	Element response = doc_.createElement(GSXML.RESPONSE_ELEM);
+	response.setAttribute(GSXML.TYPE_ATT, GSXML.REQUEST_TYPE_DESCRIBE);
+	
+	String lang = request.getAttribute(GSXML.LANG_ATT);
+	String info = request.getAttribute(GSXML.INFO_ATT);
+	String to = GSPath.getFirstLink(request.getAttribute(GSXML.TO_ATT));
+	
+	if (to.equals("")) { // to="", look at info
+	    if (info.equals(GSXML.SERVICE_ELEM+GSXML.LIST_MODIFIER)) {
+		response.appendChild(getServiceList(lang));
+		return response;
+	    } 
+	    // else error in info field
+	    System.err.println("ServiceRack describe request: error in 'info' field, info='"+info+"'.");
+	    return null;
+	} 
+
+	// describe a particular service	
+	if (service_info_map_.containsKey(to)) {
+	    response.appendChild(getServiceDescription(to, lang));
+	    return response;
+	}
+	// else error in to field
+	System.err.println("ServiceRack describe request: error in 'to' field, to='"+to+"'.");
+	return null;
+    
+    }
+
+    // the following two should be overwritten for info with any language stuff in it
+    /** returns the service list for the subclass */
+    protected Element getServiceList(String lang) {
+	// for now, it is static and there is no lang stuff
+	return short_service_info_;
+    }
+
+    /** returns a specific service description */
+    protected Element getServiceDescription(String service, String lang) {
+	Element descript = (Element)((Element)service_info_map_.get(service)).cloneNode(true);
+	// for now, create the display element on the fly - look at caching it later
+	Element display = createServiceDisplay(service, lang);
+	addServiceDisplay(descript, display);
+	return descript;
+    }
+    
+    /** adds the display element into the description - appends to the root for now, but may do something fancy later */
+    protected boolean addServiceDisplay(Element descript, Element display) {
+	if (descript.getOwnerDocument() != display.getOwnerDocument()) {
+	    display = (Element)descript.getOwnerDocument().importNode(display, true);
+	}
+	descript.appendChild(display);
+	return true;
+    }
+
+    /** creates a display element containing all the text strings needed to display the service page, in the language specified */
+    abstract protected Element createServiceDisplay(String service, String lang);
+
+    /** overloaded version for no args case */
+    protected String getTextString(String key, String lang) {
+	return getTextString(key, null, lang);
+    }
+    /** getTextString - retrieves a language specific text string for the given
+key and locale
+    */
+    protected String getTextString(String key, String[] args,  String lang) {
+
+	String class_name = this.getClass().getName();
+	class_name = class_name.substring(class_name.lastIndexOf('.')+1);
+	
+	Dictionary dict = new Dictionary(class_name, lang);
+	String result = dict.get(key, args);
+	if (result == null) { // not found
+	    return "_"+key+"_";
+	}
+	return result;
+    }
+}
