/**
*#########################################################################
* QueryResponseData.java - part of the demo-client for Greenstone 3,
* of the Greenstone digital library suite from the New Zealand Digital
* Library Project at the * University of Waikato, New Zealand.
*
* Copyright (C) 2008 New Zealand Digital Library Project
*
* 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.
*########################################################################
*/
package org.greenstone.gs3client.data;
import java.util.Vector;
import java.util.HashMap;
import org.w3c.dom.Element;
import org.greenstone.gsdl3.util.GSXML;
// 2 classes in file: public-access QueryResponseData and TermData,
// the second is a static inner class of the first.
/**
* Represents the data fields that may be present in a response
* to a Query-process request. Specifically, this class keeps track of
* all the DocumentNodes returned in response to a query request.
* It inherits Map nodeIDsToNodes of (nodeID, NodeData ref) pairs which
* maintains the NodeData object refs in order of their insertion into
* the Map (LinkedHashMap).
* !!! QueryResponseData will only store DocumentNodeData object refs in
* the nodeIDsToNodes Map.
* An object of this class can be reused after instatiation by calling
* setResponseData() with a new query response XML message. This will
* first call clear() to clear/release its references to all the old data.
* @author ak19
*/
public class QueryResponseData extends ResponseData {
/* Storing information returned about the results of a query
* in a Query response XML message */
protected String numDocsMatched;
protected String numDocsReturned;
protected String queryField; // store it just in case we ever need it
protected TermData[] termList;
/** Metadata of the query's response - not a documentNode's metadata! */
protected HashMap metadataList;
/** Default constructor */
public QueryResponseData() {
super();
metadataList = new HashMap();
}
/** Resets the internal data members of this QueryResponseData object of
* their values so that this QueryResponseData can be reused for the
* next Query response message. */
public void clear() {
super.clear(); // clears Map nodeIDsToNodes of (nodeID, NodeData ref) pairs
this.metadataList.clear();
termList = null;
numDocsMatched = numDocsReturned = queryField = "";
System.gc();
}
/** Given the response to a query message (XML with root <message>
* or <response> tag), a QueryResponseData object is created
* to store all the document Identifiers and document data returned
* as well as information about the terms that were searched on.
* Furthermore, metadata such as the number of Docs that matched and
* were returned (if any such are present in the response-message)
* are also stored.
* It first performs a clear to empty its data members and then fills
* them with the new Query response message's data.
* @param responseMessageTag is the XML DOM Element representing a query
* response XML message.
*/
public void setResponseData(Element responseMessageTag) {
this.clear(); // clear anything stored from prev search
Element listTag = ParseUtil.getFirstDescElementCalled(
responseMessageTag, GSXML.DOC_NODE_ELEM+GSXML.LIST_MODIFIER);
if(listTag == null)
return; // should not be the case, unless search term was empty
// have to go back and deal with that separately
Vector v = ParseUtil.getAllChildElementsCalled(listTag,
GSXML.DOC_NODE_ELEM);
if(v != null) {
for(int i = 0; i < v.size(); i++) {
DocumentNodeData docNode
= new DocumentNodeData((Element)v.get(i));
nodeIDsToNodes.put(docNode.nodeID, docNode);
}
v.clear();
v = null;
}
listTag = null;
// Get any term elements there might be - there may be none for
// some queries' responses
listTag = ParseUtil.getFirstDescElementCalled(responseMessageTag,
GSXML.TERM_ELEM+GSXML.LIST_MODIFIER);
if(listTag != null) {
v = ParseUtil.getAllChildElementsCalled(listTag,
GSXML.TERM_ELEM);
if(v != null) {
termList = new TermData[v.size()];
for(int i = 0; i < termList.length; i++)
termList[i] = new TermData((Element)v.get(i));
v.clear();
v = null;
}
listTag = null;
}
// Get any metadata elements there might be - there may be none
// for some queries' responses
listTag = ParseUtil.getFirstDescElementCalled(responseMessageTag,
GSXML.METADATA_ELEM+GSXML.LIST_MODIFIER);
if(listTag != null) {
v = ParseUtil.getAllChildElementsCalled(
listTag, GSXML.METADATA_ELEM);
if(v != null) {
// metadataList = new HashMap(v.size());
for(int i = 0; i < v.size(); i++) {
//create a new metadata object
MetaData meta = new MetaData((Element)v.get(i));
//add the metadata object into the HashMap keyed by its name
metadataList.put(meta.name, meta);
}
v.clear();
v = null;
}
listTag = null;
// DIFFERENT FROM MANUAL: in GS3, the value of these metadata.names
// are set in the body-text, not set as an attribute-value.
// That means, Metadata.value is not what we want, but
// Metadata.bodyText
MetaData m = (MetaData)metadataList.get("numDocsMatched");
this.numDocsMatched = (m == null) ? "" : m.bodyText;
m = (MetaData)metadataList.get("numDocsReturned");
this.numDocsReturned = (m == null) ? "" : m.bodyText;
m = (MetaData)metadataList.get("query");
this.queryField = (m == null) ? "" : m.bodyText;
} else { //empty metadataList
this.numDocsMatched = this.numDocsReturned = this.queryField = "";
}
}
/* Accessor methods */
/* Information on the search results/query-response: */
/** @return the number of matching documents for the query information */
public String getNumDocsMatched() { return numDocsMatched; }
/** @return the number of documents returned for the query information */
public String getNumDocsReturned() { return numDocsReturned; }
/** @return the query field information */
public String getQueryField() { return queryField; }
/** @return the list of termData (search terms along with frequency
* information, etc.) */
public TermData[] getTermList() { return termList; }
/** Given an nodeID, returns the DocumentNodeData object with that nodeID
* if any. Otherwise, null is returned.
* Superclass has method getNodeForID that returns NodeData instead.
* This is just a convenience method.
* @param ID is the nodeID of the DocumentNodeData to be returned.
* @return the DocumentNodeData object for the given ID or null if not
* present. */
public DocumentNodeData getDocNodeForID(String ID) {
return (DocumentNodeData)nodeIDsToNodes.get(ID);
}
/** @return an array of the DocumentNodeData objects of the documents
* returned by the executed Query Response and stored in this
* QueryResponseData object. I.e. the documentNodes of the documents in
* the search results.
* In cases of an error (such as not being able to connect to a collection
* like Infomine) this method may return an empty array (length = 0)
* if docIDsToDocNodes is empty! */
public DocumentNodeData[] getDocumentNodeList() {
DocumentNodeData[] docNodeList
= new DocumentNodeData[nodeIDsToNodes.size()];
nodeIDsToNodes.values().toArray(docNodeList);
// populates docNodeList[]
return docNodeList;
}
/** @return an array of the IDs of the list of documentNodes maintained
* by this QueryResponseData object (the documentNodes of the documents'
* in the search results).
* In cases of an error (such as not being able to connect to collection
* such as Infomine) this method may return an empty array (length = 0)
* if docIDsToDocNodes is empty! */
public String[] getDocumentNodeIDs() {
String[] docNodeIDs = new String[nodeIDsToNodes.size()];
nodeIDsToNodes.keySet().toArray(docNodeIDs);
// above statement has now populated docNodeIDs[]
return docNodeIDs;
}
/** @return metadata of the query's response. This includes values such
* as numDocsMatched, numDocsReturned, query. This does not return any
* document's metadata! */
public String getMetaValueForName(String name) {
Object o = metadataList.get(name);
if(o != null) {
MetaData m = (MetaData)o;
return m.bodyText;
}
return "";
}
/** This method can be called after a DocumentStructureRetrieve request
* (for the entire structure of all/many of its documents) has returned
* a response. Given the response message (XML) element, this method
* attempts to set the document structure for all the documentNodeData
* objects it has, using the nodeStructure tags in the
* response-message-XML. But only those documentNodeData whose nodeIDs
* are mentioned in the response-message-XML are actually set!
* This method returns a null vector if the responseMessage XMl does not
* contain any <documentNodeList> element with <documentNode>s
* each with <nodeStructure> children.
* Otherwise it returns a Vector of all the rootNodes of the list of
* docNodes that this QueryResponseData object maintains. */
public Vector setStructureForDocs(Element messageTag) {
Element docList = ParseUtil.getFirstDescElementCalled(
messageTag, GSXML.DOC_NODE_ELEM+GSXML.LIST_MODIFIER);
if(docList == null)
return null;
// Get only child elements of , don't get
// all descendent s here!
Vector docNodes = ParseUtil.getAllChildElementsCalled(
docList, GSXML.DOC_NODE_ELEM);
if(docNodes == null)
return null;
Vector rootNodes = new Vector(docNodes.size());
for(int i = 0; i < docNodes.size(); i++) {
Element docNode = (Element)docNodes.get(i);
// Now find the documentNodeData for which we are going to set
// its nodeStructure and fow which we're going to find the root
String id = (String)docNode.getAttribute(GSXML.NODE_ID_ATT);
DocumentNodeData document
= (DocumentNodeData)this.nodeIDsToNodes.get(id);
// get the element
Element nodeStructure = ParseUtil.getFirstDescElementCalled(
docNode, GSXML.NODE_STRUCTURE_ELEM);
if(nodeStructure == null || document == null)
continue; // skip to look at next docNode
// else
// nodeStructure should contain exactly one child: the root doc
document.setDescendentsOfRootNode(nodeStructure, nodeIDsToNodes);
// Now add the root of that document to our vector rootNodes
rootNodes.add(document.getRoot());
}
return rootNodes;
}
/** @return some summary info on how the search went. This may include
* how many documents matched the query, and how many of them have been
* returned. With mult-term queries, the frequencies of each separate
* term is also returned for display. */
public String toString() {
// When running a Form Search on collection gs3mgppdemo through the
// browser at http://localhost:8080/greenstone3/
// and searching on terms "snail" and "water", the output is like:
// "Word count:snail: 433, water: 608
// 11 documents matched the query. (11 documents returned.)"
// Snail and water are TermData.names, and 433 and 608 are the
//frequencies in which those terms occurred in the collection
StringBuffer buf = new StringBuffer();
if(this.termList != null) {
buf.append("Word count: ");
for(int i = 0; i < termList.length; i++)
buf.append(termList[i] + ", "); // calls toString on TermData
// get rid of final comma-space and replace with newline
buf.replace(buf.length()-2, buf.length(), "\n");
}
if(!this.numDocsMatched.equals("")) {
buf.append(this.numDocsMatched);
buf.append(" document(s) matched the query.");
}
if(!this.numDocsReturned.equals("")) {
buf.append(" (");
buf.append(this.numDocsReturned);
buf.append(" document(s) returned.)");
}
return buf.toString();
}
/** Static inner class Term represents a <term> XML element
* (these are nested in a <termList>) - see manual p. 45:
* <term name="str" numDocsMatched="int" freq="int" field="int" stem="int"/>
* <equivTermList>
* <term name="str" numDocsMatched="int" freq="int" />
* <term name="str" numDocsMatched="int" freq="int" />
* ...
* </equivTermList>
* </term>
* Can import this class as import gs3client.QueryResponseData.TermData
* and can then use it just as "TermData" (don't need fully qualified name).
*/
public static class TermData {
/* Information about the TermData */
public final String name;
public final String numDocsMatch;
public final String freq;
// Member fields that might not always be set (may be ""):
public final String field;
public final String stem;
/** The terms nested inside an <equivTermList> */
protected TermData[] equivTermList;
/** Constructs a Term object to represent the <term> element passed
* in here as an argument. Given a <term></term> element, it
* sets this object's members.
* @param termTag is a <term></term> element */
public TermData(Element termTag) {
this.name = termTag.hasAttribute(GSXML.NAME_ATT) ?
termTag.getAttribute(GSXML.NAME_ATT) : "";
this.field = termTag.hasAttribute("field") ?
termTag.getAttribute("field") : "";
this.stem = termTag.hasAttribute("stem") ?
termTag.getAttribute("stem") : "";
this.freq = termTag.hasAttribute("freq") ?
termTag.getAttribute("freq") : "";
//Integer.parseInt(termTag.getAttribute("freq")) : 0;
this.numDocsMatch = termTag.hasAttribute("numDocsMatch") ?
termTag.getAttribute("numDocsMatch") : "";
//Integer.parseInt(termTag.getAttribute("numDocsMatch")) : 0;
setEquivTermList(termTag);
}
/** Uses the <equivTermList>...</equivTermList> tag,
* which may cotnain more TermData.
* @param termTag is a <term></term> element that may
* contain a <equivTermList>...</equivTermList> element */
public void setEquivTermList(Element termTag) {
Element equivList = ParseUtil.getFirstChildElementCalled(
termTag, "equivTerm"+GSXML.LIST_MODIFIER);
if(equivList != null) {
Vector equivTerms = ParseUtil.getAllChildElementsCalled(
equivList, GSXML.TERM_ELEM); // children
if(equivTerms != null) {
equivTermList = new TermData[equivTerms.size()];
for(int i = 0; i < equivTermList.length; i++)
equivTermList[i] = new TermData(
(Element)equivTerms.get(i));
}
}
}
/** @return any list of <equivTermList>...</equivTermList>
* TermData maintained by this TermData object. Null if there are none. */
public TermData[] getEquivTermList() { return equivTermList; }
// Frequency is always a number
/** @return a String representation of this TermData: the name
* and frequency */
public String toString() { return this.name + ": " + this.freq; }
/** @return a String displaying the member contents of this TermData.
* Useful for debugging purposes. */
public String show() {
StringBuffer buf = new StringBuffer("name: " + this.name);
buf.append(" numDocsMatched: " + this.numDocsMatch);
buf.append(" freq: " + freq);
buf.append(" field: " + field);
buf.append(" stem: " + stem + "\n");
if(equivTermList != null) {
buf.append("EquivTermList:\n");
for(int i = 0; i < equivTermList.length; i++)
buf.append(" " + equivTermList[i].show() + "\n");
}
return buf.toString();
}
}
}