source: main/trunk/greenstone3/src/java/org/greenstone/gsdl3/service/AbstractSearch.java@ 32661

Last change on this file since 32661 was 32547, checked in by kjdon, 6 years ago

paramDefault elements for searching can now go in the format element instead. This makes them in hte same place as for display. Also means you can edit them in gli

  • Property svn:keywords set to Author Date Id Revision
File size: 14.3 KB
Line 
1/*
2 * AbstractSearch.java
3 * Copyright (C) 2005 New Zealand Digital Library, http://www.nzdl.org
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18 */
19package org.greenstone.gsdl3.service;
20
21// Greenstone classes
22import java.util.ArrayList;
23import java.util.HashMap;
24
25import org.apache.log4j.Logger;
26import org.greenstone.gsdl3.util.AbstractBasicDocument;
27import org.greenstone.gsdl3.util.BasicDocument;
28import org.greenstone.gsdl3.util.GSPath;
29import org.greenstone.gsdl3.util.GSXML;
30import org.greenstone.gsdl3.util.XMLConverter;
31import org.w3c.dom.Document;
32import org.w3c.dom.Element;
33import org.w3c.dom.NodeList;
34
35/**
36 * Partially implements a generic search service
37 *
38 */
39
40public abstract class AbstractSearch extends ServiceRack
41{
42
43 static Logger logger = Logger.getLogger(org.greenstone.gsdl3.service.AbstractSearch.class.getName());
44
45 // the search service
46 protected String QUERY_SERVICE = null; // set by subclass
47
48 // compulsory params
49 protected static final String INDEX_PARAM = "index";
50 protected static final String QUERY_PARAM = "query";
51 protected static final String RAW_PARAM = "rawquery";
52
53 // optional standard params - some of these have to be implemented
54 protected static final String MAXDOCS_PARAM = "maxDocs";
55 protected static final String HITS_PER_PAGE_PARAM = "hitsPerPage";
56 protected static final String START_PAGE_PARAM = "startPage";
57
58 protected AbstractBasicDocument gs_doc = null;
59
60 /** can more than one index be searched at the same time? */
61 protected boolean does_multi_index_search = false;
62 /** does this service support paging of results? */
63 protected boolean does_paging = false;
64 /** does this service support asking for a subset of results? */
65 protected boolean does_chunking = false;
66 /** does this service support faceting search results */
67 protected boolean does_faceting = false;
68 /** does this service support highlighting snippets results */
69 protected boolean does_highlight_snippets = false;
70 protected boolean does_full_field_highlighting = false;
71 /**
72 * the default document type - use if all documents are the same type
73 */
74 protected String default_document_type = null;
75 /**
76 * the default index, or comma separated list if more than one is the
77 * default (with start and end commas, eg ,TI,SU,). Should be set by
78 * configure()
79 */
80 protected String default_index = "";
81
82 protected Element service_metadata_list = null;
83 protected HashMap<String, String> paramDefaults = null;
84
85 public AbstractSearch()
86 {
87 paramDefaults = new HashMap<String, String>();
88 }
89
90 /**
91 * Sets up the short service info for service by QUERY_SERVICE (e.g.
92 * TextQuery or AudioQuery) If other services will be provided, should be
93 * added in the subclass configure also looks for search format info, and
94 * document format info
95 */
96 public boolean configure(Element info, Element extra_info)
97 {
98 if (!super.configure(info, extra_info))
99 {
100 return false;
101 }
102
103 logger.info("Configuring AbstractSearch...");
104
105 this.config_info = info;
106
107 // set up short_service_info_
108 // => for now just has id and type. the name (lang dependent)
109 // will be added in if the list is requested.
110
111 Element tq_service = this.desc_doc.createElement(GSXML.SERVICE_ELEM);
112 tq_service.setAttribute(GSXML.TYPE_ATT, GSXML.SERVICE_TYPE_QUERY);
113 tq_service.setAttribute(GSXML.NAME_ATT, QUERY_SERVICE);
114 this.short_service_info.appendChild(tq_service);
115
116 // load up paging defaults
117 if (does_chunking) {
118 paramDefaults.put(MAXDOCS_PARAM, "100");
119 }
120 if (does_paging) {
121 paramDefaults.put(HITS_PER_PAGE_PARAM, "20");
122 paramDefaults.put(START_PAGE_PARAM, "1");
123 }
124
125 // load up any search param defaults
126 Element search_elem = (Element) GSXML.getChildByTagName(extra_info, GSXML.SEARCH_ELEM);
127 if (search_elem != null) {
128 getSearchParamDefaults(search_elem);
129 }
130
131 // store any that we need to save
132 // previously these were all saved by default, so adding them all
133 this.save_params.add(INDEX_PARAM);
134 this.save_params.add(QUERY_PARAM);
135 this.save_params.add(RAW_PARAM);
136 this.save_params.add(MAXDOCS_PARAM);
137 this.save_params.add(HITS_PER_PAGE_PARAM);
138 this.save_params.add(START_PAGE_PARAM);
139
140 // add some format info to service map if there is any
141 // => lookin extra info first look in buildConfig
142
143 Element format = (Element) GSXML.getChildByTagName(info, GSXML.FORMAT_ELEM);
144
145 if (format == null)
146 {
147 // try to find a format element inside <search> that contains a gsf:template. Note what if we have only xsl:templates??
148
149 NodeList format_elems = null;
150 if (search_elem != null)
151 {
152 format_elems = search_elem.getElementsByTagName(GSXML.FORMAT_ELEM);
153 }
154 for (int i = 0; i < format_elems.getLength(); i++)
155 {
156 format = (Element) format_elems.item(i);
157 if (format.getElementsByTagName("gsf:template").getLength() != 0)
158 {
159 break;
160 }
161 }
162 }//end of if(format==null)
163 //
164 if (format != null)
165 {
166 this.format_info_map.put(QUERY_SERVICE, this.desc_doc.importNode(format, true));
167 }
168
169 // look for document display format - for documentType
170 String path = GSPath.appendLink(GSXML.DISPLAY_ELEM, GSXML.FORMAT_ELEM);
171 Element display_format = (Element) GSXML.getNodeByPath(extra_info, path);
172 if (display_format != null)
173 {
174 // check for docType option.
175 Element doc_type_opt = GSXML.getNamedElement(display_format, "gsf:option", GSXML.NAME_ATT, "documentType");
176 if (doc_type_opt != null)
177 {
178 String value = doc_type_opt.getAttribute(GSXML.VALUE_ATT);
179 if (!value.equals(""))
180 {
181 this.default_document_type = value;
182 }
183 }
184 }
185
186 // Base line for document (might be overriden by sub-classes)
187 gs_doc = new BasicDocument(this.default_document_type);
188
189 return true;
190 }
191
192 protected void getSearchParamDefaults(Element search_elem) {
193
194 NodeList param_defaults_list = search_elem.getElementsByTagName(GSXML.PARAM_DEFAULT_ELEM); //GSXML.getChildrenByTagName(search_elem, GSXML.PARAM_DEFAULT_ELEM);
195 for (int i=0; i<param_defaults_list.getLength(); i++) {
196 Element paramdef = (Element)param_defaults_list.item(i);
197 String name = paramdef.getAttribute(GSXML.NAME_ATT);
198 String val = paramdef.getAttribute(GSXML.VALUE_ATT);
199 if (!name.equals("") && !val.equals("")) {
200 paramDefaults.put(name, val);
201 }
202 }
203 }
204 /**
205 * returns a basic description for QUERY_SERVICE. If a subclass provides
206 * other services they need to provide their own descriptions
207 */
208 protected Element getServiceDescription(Document doc, String service, String lang, String subset)
209 {
210 if (!service.equals(QUERY_SERVICE))
211 {
212 return null;
213 }
214
215 Element tq_service = doc.createElement(GSXML.SERVICE_ELEM);
216 tq_service.setAttribute(GSXML.TYPE_ATT, GSXML.SERVICE_TYPE_QUERY);
217 tq_service.setAttribute(GSXML.NAME_ATT, QUERY_SERVICE);
218 if (subset == null || subset.equals(GSXML.DISPLAY_TEXT_ELEM + GSXML.LIST_MODIFIER))
219 {
220 tq_service.appendChild(GSXML.createDisplayTextElement(doc, GSXML.DISPLAY_TEXT_NAME, getServiceName(QUERY_SERVICE, lang)));
221 tq_service.appendChild(GSXML.createDisplayTextElement(doc, GSXML.DISPLAY_TEXT_SUBMIT, getServiceSubmit(QUERY_SERVICE, lang)));
222 tq_service.appendChild(GSXML.createDisplayTextElement(doc, GSXML.DISPLAY_TEXT_DESCRIPTION, getServiceDescription(QUERY_SERVICE, lang)));
223 }
224 if (subset == null || subset.equals(GSXML.PARAM_ELEM + GSXML.LIST_MODIFIER))
225 {
226 Element param_list = doc.createElement(GSXML.PARAM_ELEM + GSXML.LIST_MODIFIER);
227 addCustomQueryParams(param_list, lang);
228 addStandardQueryParams(param_list, lang);
229 tq_service.appendChild(param_list);
230 }
231 if (subset == null || subset.equals(GSXML.METADATA_ELEM + GSXML.LIST_MODIFIER)) {
232
233 if (service_metadata_list == null) {
234 Document ml_doc = XMLConverter.newDOM();
235 service_metadata_list = ml_doc.createElement(GSXML.METADATA_ELEM+GSXML.LIST_MODIFIER);
236 if (does_paging) {
237 service_metadata_list.appendChild(GSXML.createMetadataElement(ml_doc, "does_paging", "true"));
238 }
239 }
240 tq_service.appendChild(doc.importNode(service_metadata_list, true));
241 }
242 return tq_service;
243
244 }
245
246 // perhaps these should be changed to search down the class hierarchy for
247 // values - do can just put the info in the resource bundle to use it
248 /** returns the default name for the TextQuery service */
249 protected String getServiceName(String service_id, String lang)
250 {
251 return getTextString(service_id + ".name", lang);
252 }
253
254 /** returns the default description for the TextQuery service */
255 protected String getServiceDescription(String service_id, String lang)
256 {
257 return getTextString(service_id + ".description", lang);
258 }
259
260 /** returns the default submit button text for the TextQuery service */
261 protected String getServiceSubmit(String service_id, String lang)
262 {
263 return getTextString(service_id + ".submit", lang);
264
265 }
266
267 /** adds the standard query params into the service description */
268 protected void addStandardQueryParams(Element param_list, String lang)
269 {
270 // this test is not so good. here we are using absence of default index
271 // to determine whether we have indexes or not. But in other places,
272 // absence of default index just means to use the first one as default.
273 if (!default_index.equals(""))
274 {
275 createParameter(INDEX_PARAM, param_list, lang);
276 }
277 if (does_chunking)
278 {
279 createParameter(MAXDOCS_PARAM, param_list, lang);
280 }
281 if (does_paging)
282 {
283 createParameter(HITS_PER_PAGE_PARAM, param_list, lang);
284 createParameter(START_PAGE_PARAM, param_list, lang);
285 }
286 createParameter(QUERY_PARAM, param_list, lang);
287 }
288
289 /**
290 * adds any service specific query params into the service default
291 * implementation: add nothing. subclasses may need to override this to add
292 * in their specific parameters
293 */
294 protected void addCustomQueryParams(Element param_list, String lang)
295 {
296 // default behaviour, do nothing
297 }
298
299 protected void createParameter(String name, Element param_list, String lang)
300 {
301 createParameter(name, param_list, lang, null);
302 }
303
304 protected void createParameter(String name, Element param_list, String lang, String default_value)
305 {
306 // at this level, not interested in boolean return type
307 createParameterChain(name, param_list, lang, default_value);
308 }
309
310 /**
311 * default implementations for the standard parameters plus some other
312 * common ones index, maxDocs, hitsPerPage, startPage
313 */
314
315 protected boolean createParameterChain(String name, Element param_list, String lang, String default_value)
316 {
317 Document doc = param_list.getOwnerDocument();
318 Element param = null;
319 String param_default = default_value;
320 if (default_value == null) {
321 // have we got a stored up default? will be null if not there
322 param_default = paramDefaults.get(name);
323 }
324 if (name.equals(QUERY_PARAM) || name.equals(RAW_PARAM))
325 {
326 param = GSXML.createParameterDescription(doc, name, getTextString("param." + name, lang), GSXML.PARAM_TYPE_STRING, param_default, null, null);
327 param_list.appendChild(param);
328 return true;
329 }
330 else if (name.equals(INDEX_PARAM))
331 {
332 // should we make these class fields?
333 ArrayList<String> index_ids = new ArrayList<String>();
334 ArrayList<String> index_names = new ArrayList<String>();
335 getIndexData(index_ids, index_names, lang);
336 String param_type = GSXML.PARAM_TYPE_ENUM_SINGLE;
337 if (does_multi_index_search)
338 {
339 param_type = GSXML.PARAM_TYPE_ENUM_MULTI;
340 }
341 if (param_default == null)
342 {
343 param_default = this.default_index;
344 }
345 param = GSXML.createParameterDescription2(doc, INDEX_PARAM, getTextString("param." + INDEX_PARAM, lang), param_type, param_default, index_ids, index_names);
346 param_list.appendChild(param);
347 return true;
348 }
349 else if (name.equals(MAXDOCS_PARAM))
350 {
351 param = GSXML.createParameterDescription(doc, name, getTextString("param." + name, lang), GSXML.PARAM_TYPE_INTEGER, param_default, null, null);
352 param_list.appendChild(param);
353 return true;
354 }
355 else if (name.equals(HITS_PER_PAGE_PARAM))
356 {
357 param = GSXML.createParameterDescription(doc, name, getTextString("param." + name, lang), GSXML.PARAM_TYPE_INTEGER, param_default, null, null);
358 param_list.appendChild(param);
359 return true;
360 }
361 else if (name.equals(START_PAGE_PARAM))
362 {
363 // start page - set to 1 for the search page
364 param = GSXML.createParameterDescription(doc, START_PAGE_PARAM, "", GSXML.PARAM_TYPE_INVISIBLE, param_default, null, null);
365 param_list.appendChild(param);
366 return true;
367 }
368
369 // Get to there then none of the above params matched
370 // => return false so the chain can continue
371 return false;
372 }
373
374 /**
375 * create an element to go into the search results list. A node element has
376 * the form <docNode nodeId='xxx' nodeType='leaf' docType='hierarchy'
377 * rank='0.23'/>
378 */
379 protected Element createDocNode(Document doc, String node_id, String rank)
380 {
381 return this.gs_doc.createDocNode(doc, node_id, rank);
382 }
383
384 /**
385 * returns the document type of the doc that the specified node belongs to.
386 * should be one of GSXML.DOC_TYPE_SIMPLE, GSXML.DOC_TYPE_PAGED,
387 * GSXML.DOC_TYPE_HIERARCHY
388 */
389 protected String getDocType(String node_id)
390 {
391 return this.gs_doc.getDocType(node_id);
392 }
393
394 /**
395 * returns the node type of the specified node. should be one of
396 * GSXML.NODE_TYPE_LEAF, GSXML.NODE_TYPE_INTERNAL, GSXML.NODE_TYPE_ROOT
397 */
398 protected String getNodeType(String node_id, String doc_type)
399 {
400 return this.gs_doc.getNodeType(node_id, doc_type);
401 }
402
403 /** returns true if the node has child nodes */
404 protected boolean hasChildren(String node_id)
405 {
406 return this.gs_doc.hasChildren(node_id);
407 }
408
409 /** returns true if the node has a parent */
410 protected boolean hasParent(String node_id)
411 {
412 return this.gs_doc.hasParent(node_id);
413 }
414
415 /**
416 * get the details about the indexes available must be implemented by
417 * subclass there must be at least one index
418 */
419 abstract protected void getIndexData(ArrayList<String> index_ids, ArrayList<String> index_names, String lang);
420
421}
Note: See TracBrowser for help on using the repository browser.