Index: main/trunk/greenstone3/src/java/org/greenstone/gsdl3/service/CrossCollectionSearch.java
===================================================================
--- main/trunk/greenstone3/src/java/org/greenstone/gsdl3/service/CrossCollectionSearch.java	(revision 32655)
+++ main/trunk/greenstone3/src/java/org/greenstone/gsdl3/service/CrossCollectionSearch.java	(revision 32656)
@@ -1,3 +1,257 @@
-
+/*
+ *    CrossCollectionSearch.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;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.log4j.Logger;
+import org.greenstone.gsdl3.util.GSPath;
+import org.greenstone.gsdl3.util.GSXML;
+import org.greenstone.gsdl3.util.UserContext;
+import org.greenstone.gsdl3.util.XMLConverter;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+/**
+ * This ServiceRack gets specified in siteConfig.xml. So it is loaded by the MessaegRouter, and two services get activated: TextQuery, DocumentMetadataRetrieve.
+These are located at MR level, not inside a collection. QueryAction will send messages to "TextQuery", rather than eg "mgppdemo/TextQuery".
+These two services will requery the MR for search results/document metadata based on collections or documents listed.
+ */
+
+public class CrossCollectionSearch extends ServiceRack
+{
+
+	static Logger logger = Logger.getLogger(org.greenstone.gsdl3.service.CrossCollectionSearch.class.getName());
+	protected static final String QUERY_PARAM = "query";
+	protected static final String COLLECTION_PARAM = "collection";
+	protected static final String GROUP_PARAM = "group";
+  protected static final String MAXDOCS_PARAM = "maxDocs"; // matches standard maxDocs, but in this case, means max docs per collection
+  protected static final String HITS_PER_PAGE_PARAM = "hitsPerPage";
+  protected static final String MAXDOCS_DEFAULT = "20";
+	// the services on offer - these proxy the actual collection ones
+	protected static final String TEXT_QUERY_SERVICE = "TextQuery";
+	protected static final String DOCUMENT_METADATA_RETRIEVE_SERVICE = "DocumentMetadataRetrieve";
+
+	protected String[] coll_ids_list = null;
+	protected String[] coll_ids_list_no_all = null;
+	// maps lang to coll names list
+	protected HashMap<String, String[]> coll_names_map = null;
+
+	//protected String[] coll_names_list = null;
+
+	/** constructor */
+	public CrossCollectionSearch()
+	{
+	}
+
+	public boolean configure(Element info, Element extra_info)
+	{
+		// any parameters? colls to include??
+		logger.info("Configuring CrossCollectionSearch...");
+		// query service
+		Element ccs_service = this.desc_doc.createElement(GSXML.SERVICE_ELEM);
+		ccs_service.setAttribute(GSXML.TYPE_ATT, GSXML.SERVICE_TYPE_QUERY);
+		ccs_service.setAttribute(GSXML.NAME_ATT, TEXT_QUERY_SERVICE);
+		this.short_service_info.appendChild(ccs_service);
+
+		// metadata service
+		Element dmr_service = this.desc_doc.createElement(GSXML.SERVICE_ELEM);
+		dmr_service.setAttribute(GSXML.TYPE_ATT, GSXML.SERVICE_TYPE_RETRIEVE);
+		dmr_service.setAttribute(GSXML.NAME_ATT, DOCUMENT_METADATA_RETRIEVE_SERVICE);
+		this.short_service_info.appendChild(dmr_service);
+
+
+		// what params do we want saved in the session?
+		this.save_params.add(QUERY_PARAM);
+		this.save_params.add(COLLECTION_PARAM);
+		this.save_params.add(GROUP_PARAM);
+		this.save_params.add(MAXDOCS_PARAM);
+		this.save_params.add(HITS_PER_PAGE_PARAM);
+		
+		// get any format info
+		Element format_info = (Element) GSXML.getChildByTagName(info, GSXML.FORMAT_ELEM);
+		if (format_info != null)
+		{
+			this.format_info_map.put(TEXT_QUERY_SERVICE, this.desc_doc.importNode(format_info, true));
+		}
+		else
+		{
+			// add in a default format statement
+		  //"xmlns:gsf='" + GSXML.GSF_NAMESPACE + "' xmlns:xsl='" + GSXML.XSL_NAMESPACE + "
+		  String format_string = "<format "+GSXML.STD_NAMESPACES_ATTS + "><gsf:template match='documentNode'><td><a><xsl:attribute name='href'>?a=d&amp;c=<xsl:value-of select='@collection'/>&amp;d=<xsl:value-of select='@nodeID'/><xsl:if test=\"@nodeType='leaf'\">&amp;sib=1</xsl:if>&amp;dt=<xsl:value-of select='@docType'/>&amp;p.a=q&amp;p.s=" + TEXT_QUERY_SERVICE + "&amp;p.c=";
+			if (this.cluster_name != null)
+			{
+				format_string += this.cluster_name;
+			}
+			format_string += "</xsl:attribute><gsf:icon/></a></td><td><gsf:metadata name='Title'/> (<xsl:value-of select='@collection'/>) </td></gsf:template></format>";
+			this.format_info_map.put(TEXT_QUERY_SERVICE, this.desc_doc.importNode(this.converter.getDOM(format_string).getDocumentElement(), true));
+		}
+		return true;
+	}
+
+  protected Element getServiceDescription(Document doc, String service, String lang, String subset)
+	{
+		if (service.equals(TEXT_QUERY_SERVICE))
+		{
+
+			Element ccs_service = doc.createElement(GSXML.SERVICE_ELEM);
+			ccs_service.setAttribute(GSXML.TYPE_ATT, GSXML.SERVICE_TYPE_QUERY);
+			ccs_service.setAttribute(GSXML.NAME_ATT, TEXT_QUERY_SERVICE);
+
+			// display info
+			if (subset == null || subset.equals(GSXML.DISPLAY_TEXT_ELEM + GSXML.LIST_MODIFIER))
+			{
+				ccs_service.appendChild(GSXML.createDisplayTextElement(doc, GSXML.DISPLAY_TEXT_NAME, getTextString(TEXT_QUERY_SERVICE + ".name", lang)));
+				ccs_service.appendChild(GSXML.createDisplayTextElement(doc, GSXML.DISPLAY_TEXT_SUBMIT, getTextString(TEXT_QUERY_SERVICE + ".submit", lang)));
+				ccs_service.appendChild(GSXML.createDisplayTextElement(doc, GSXML.DISPLAY_TEXT_DESCRIPTION, getTextString(TEXT_QUERY_SERVICE + ".description", lang)));
+			}
+			// param info
+			if (subset == null || subset.equals(GSXML.PARAM_ELEM + GSXML.LIST_MODIFIER))
+			{
+				Element param_list = doc.createElement(GSXML.PARAM_ELEM + GSXML.LIST_MODIFIER);
+				// collection list
+				if (coll_ids_list == null)
+				{
+					initCollectionList(lang);
+				}
+				if (!coll_names_map.containsKey(lang))
+				{
+					addCollectionNames(lang);
+				}
+				Element param = GSXML.createParameterDescription(doc, COLLECTION_PARAM, getTextString("param." + COLLECTION_PARAM, lang), GSXML.PARAM_TYPE_ENUM_MULTI, "all", coll_ids_list, coll_names_map.get(lang));
+				param_list.appendChild(param);
+				// max docs param
+				param = GSXML.createParameterDescription(doc, MAXDOCS_PARAM, getTextString("param." + MAXDOCS_PARAM, lang), GSXML.PARAM_TYPE_INTEGER, MAXDOCS_DEFAULT, null, null);
+				param_list.appendChild(param);
+				// query param
+				param = GSXML.createParameterDescription(doc, QUERY_PARAM, getTextString("param." + QUERY_PARAM, lang), GSXML.PARAM_TYPE_STRING, null, null, null);
+				param_list.appendChild(param);
+				ccs_service.appendChild(param_list);
+			}
+
+			logger.debug("service description=" + this.converter.getPrettyString(ccs_service));
+			return ccs_service;
+		}
+		// these ones are probably never called, but put them here just in case
+		Element service_elem = doc.createElement(GSXML.SERVICE_ELEM);
+		service_elem.setAttribute(GSXML.TYPE_ATT, GSXML.SERVICE_TYPE_RETRIEVE);
+		service_elem.setAttribute(GSXML.NAME_ATT, service);
+		return service_elem;
+
+	}
+
+	protected Element processTextQuery(Element request)
+	{
+		// Create a new (empty) result message
+	  Document result_doc = XMLConverter.newDOM();
+		Element result = result_doc.createElement(GSXML.RESPONSE_ELEM);
+		result.setAttribute(GSXML.FROM_ATT, TEXT_QUERY_SERVICE);
+		result.setAttribute(GSXML.TYPE_ATT, GSXML.REQUEST_TYPE_PROCESS);
+
+		UserContext userContext = new UserContext(request);
+
+		// Get the parameters of the request
+		Element param_list = (Element) GSXML.getChildByTagName(request, GSXML.PARAM_ELEM + GSXML.LIST_MODIFIER);
+		if (param_list == null)
+		{
+			logger.error("TextQuery request had no paramList.");
+			return result; // Return the empty result
+		}
+		// get the collection list
+		String[] colls_list = coll_ids_list_no_all;
+		Element coll_param = GSXML.getNamedElement(param_list, GSXML.PARAM_ELEM, GSXML.NAME_ATT, COLLECTION_PARAM);
+		if (coll_param != null)
+		{
+			String coll_list = GSXML.getValue(coll_param);
+			if (!coll_list.equals("all") && !coll_list.equals(""))
+			{
+				colls_list = coll_list.split(",");
+			}
+		}
+			
+		colls_list = mergeGroups(userContext, param_list, colls_list);
+		
+		String maxdocs = MAXDOCS_DEFAULT;
+		Element maxdocs_param = GSXML.getNamedElement(param_list, GSXML.PARAM_ELEM, GSXML.NAME_ATT, MAXDOCS_PARAM);
+		if (maxdocs_param != null) {
+		  maxdocs = GSXML.getValue(maxdocs_param);
+		}
+		
+		Document msg_doc = XMLConverter.newDOM();
+		Element query_message = msg_doc.createElement(GSXML.MESSAGE_ELEM);
+		// we are sending the same request to each collection - build up the to
+		// attribute for the request
+		StringBuffer to_att = new StringBuffer();
+		for (int i = 0; i < colls_list.length; i++)
+		{
+			if (i > 0)
+			{
+				to_att.append(",");
+			}
+			to_att.append(GSPath.appendLink(colls_list[i], "TextQuery"));
+
+		}
+		// send the query to all colls
+		Element query_request = GSXML.createBasicRequest(msg_doc, GSXML.REQUEST_TYPE_PROCESS, to_att.toString(), userContext);
+		query_message.appendChild(query_request);
+		// should we add params individually?
+		Element new_param_list = msg_doc.createElement(GSXML.PARAM_ELEM + GSXML.LIST_MODIFIER);
+		query_request.appendChild(new_param_list);
+		new_param_list.appendChild(msg_doc.importNode(GSXML.getNamedElement(param_list, GSXML.PARAM_ELEM, GSXML.NAME_ATT, QUERY_PARAM), true));
+
+		// for cross coll search, we only want maxdocs from each collection
+		// some colls use maxdocs, some use hits per page so lets send both
+		new_param_list.appendChild(GSXML.createParameter(msg_doc, MAXDOCS_PARAM, maxdocs));
+		new_param_list.appendChild(GSXML.createParameter(msg_doc, HITS_PER_PAGE_PARAM, maxdocs));
+		Element query_result = (Element) this.router.process(query_message);
+		// create the doc list for the response
+		Element doc_node_list = result_doc.createElement(GSXML.DOC_NODE_ELEM + GSXML.LIST_MODIFIER);
+		result.appendChild(doc_node_list);
+		Element result_snippet_list = result_doc.createElement(GSXML.HL_SNIPPET_ELEM + GSXML.LIST_MODIFIER);
+		result.appendChild(result_snippet_list);
+
+		NodeList responses = query_result.getElementsByTagName(GSXML.RESPONSE_ELEM);
+		int num_docs = 0;
+		for (int k = 0; k < responses.getLength(); k++)
+		{
+			String coll_name = GSPath.removeLastLink(((Element) responses.item(k)).getAttribute(GSXML.FROM_ATT));
+			NodeList nodes = ((Element) responses.item(k)).getElementsByTagName(GSXML.DOC_NODE_ELEM);
+			if (nodes == null || nodes.getLength() == 0)
+				continue;
+			num_docs += nodes.getLength();
+			Element last_node = null;
+			Element this_node = null;
+			for (int n = 0; n < nodes.getLength(); n++)
+			{
+				this_node = (Element) nodes.item(n);
+				this_node.setAttribute("collection", coll_name);
+	
+				if (k == 0)
+				{
+
+					doc_node_list.appendChild(result_doc.importNode(this_node, true));
+				}
 				else
 				{
@@ -11,12 +265,12 @@
 			}
 			NodeList hl_snippet_list = ((Element) responses.item(k)).getElementsByTagName(GSXML.HL_SNIPPET_ELEM);
-			if (hl_snippet_list != null) {
-			  for (int hls = 0; hls < hl_snippet_list.getLength(); hls++) {
+			if (hl_snippet_list != null){
+			  for (int hls = 0; hls < hl_snippet_list.getLength(); hls++){
 			    Element this_hls = (Element) hl_snippet_list.item(hls);
 			    this_hls.setAttribute("collection", coll_name);
 			    result_snippet_list.appendChild(result_doc.importNode(this_hls, true));
-			  }
-			}
-			
+			}
+		}
+
 		}
 		// just send back num docs returned. Too hard to work out number of matches etc as each index type
@@ -326,6 +580,6 @@
 				String value = groupParam.getAttribute(GSXML.VALUE_ATT);
 				String group = collectionParam.substring(prefix.length() - 1 );
-				group=group.replace('.', '/'); // we use . instead of / in args
-				value += "," + group;
+ 				group=group.replace('.', '/'); // we use . instead of / in args
+ 				value += "," + group;
 				groupParam.setAttribute(GSXML.VALUE_ATT, value);
 			}
