/** *######################################################################### * 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(); } } }