source: main/trunk/greenstone3/src/java/org/greenstone/gsdl3/action/GS2BrowseAction.java@ 28382

Last change on this file since 28382 was 28382, checked in by davidb, 11 years ago

Elimination of the 'this.doc' field from the Action baseclass and the subclasses that rely on it. For Greenstone3 purposes it is unsafe to create this object in the constructor to the action and then store it for other methods to access. This is because the Greenstone 3 (and in particular calls to 'process' operate in a multi-threaded context, that is managed by the Servlet server (e.g. Tomcat by default). Calls to DOM methods are not guaranteed to be thread safe, this became apparent when we started looking in to an exception that was being thrown, and centred around use of the DOM method 'item(i)'. The change this commit makes is to remove 'this.doc' being stored as a field. A document is now created in the top level of a call to 'process()' and when a DOM reference is needed in a subsequent method an Element variable (typically passed in as a parameter to the method) is used (through 'Document doc = element.getOwnerDocument()') to gain access to the DOM

  • Property svn:keywords set to Author Date Id Revision
File size: 18.9 KB
Line 
1package org.greenstone.gsdl3.action;
2
3import java.io.Serializable;
4import java.util.HashMap;
5import java.util.HashSet;
6
7import org.apache.log4j.Logger;
8import org.greenstone.gsdl3.util.GSParams;
9import org.greenstone.gsdl3.util.GSPath;
10import org.greenstone.gsdl3.util.GSXML;
11import org.greenstone.gsdl3.util.GSXSLT;
12import org.greenstone.gsdl3.util.OID;
13import org.greenstone.gsdl3.util.UserContext;
14import org.w3c.dom.Document;
15import org.w3c.dom.Element;
16import org.w3c.dom.Node;
17import org.w3c.dom.NodeList;
18
19/** action for GS2 style classifier browsing */
20public class GS2BrowseAction extends Action
21{
22
23 public static final String CLASSIFIER_ARG = "cl";
24
25 public static final String DESCENDANTS_ARG = "descendants";
26
27 static Logger logger = Logger.getLogger(org.greenstone.gsdl3.action.GS2BrowseAction.class.getName());
28
29 /** process the request */
30 public Node process(Node message_node)
31 {
32 Element message = this.converter.nodeToElement(message_node);
33 Document doc = message.getOwnerDocument();
34
35 // get the request - assume only one
36 Element request = (Element) GSXML.getChildByTagName(message, GSXML.REQUEST_ELEM);
37
38 // the result
39 Element result = doc.createElement(GSXML.MESSAGE_ELEM);
40 Element response = classifierBrowse(request);
41 result.appendChild(response);
42 return result;
43 }
44
45 protected Element classifierBrowse(Element request)
46 {
47 Document doc = request.getOwnerDocument();
48 Element page_response = doc.createElement(GSXML.RESPONSE_ELEM);
49
50 // extract the params from the cgi-request, and check that we have a coll specified
51 Element cgi_paramList = (Element) GSXML.getChildByTagName(request, GSXML.PARAM_ELEM + GSXML.LIST_MODIFIER);
52 HashMap<String, Serializable> params = GSXML.extractParams(cgi_paramList, false);
53
54 String service_name = (String) params.get(GSParams.SERVICE);
55 String collection = (String) params.get(GSParams.COLLECTION);
56 if (collection == null || collection.equals(""))
57 {
58 logger.error("classifierBrowse, need to specify a collection!");
59 return page_response;
60 }
61
62 boolean get_descendants = false;
63 String descendants_str = (String) params.get(DESCENDANTS_ARG);
64 if (descendants_str != null && descendants_str.equals("1"))
65 {
66 get_descendants = true;
67 }
68
69 UserContext userContext = new UserContext(request);
70 String to = GSPath.appendLink(collection, service_name);
71
72 // the first part of the response is the service description
73 // for now get this again from the service.
74 // this should be cached somehow later on.
75
76 Element info_message = doc.createElement(GSXML.MESSAGE_ELEM);
77 Element info_request = GSXML.createBasicRequest(doc, GSXML.REQUEST_TYPE_DESCRIBE, to, userContext);
78 info_message.appendChild(info_request);
79
80 // also get the format stuff now if there is some
81 Element format_request = GSXML.createBasicRequest(doc, GSXML.REQUEST_TYPE_FORMAT, to, userContext);
82 info_message.appendChild(format_request);
83 // process the requests
84
85 Element info_response = (Element) this.mr.process(info_message);
86
87 // the two responses
88 NodeList responses = info_response.getElementsByTagName(GSXML.RESPONSE_ELEM);
89 Element service_response = (Element) responses.item(0);
90 Element format_response = (Element) responses.item(1);
91
92 Element service_description = (Element) GSXML.getChildByTagName(service_response, GSXML.SERVICE_ELEM);
93 page_response.appendChild(doc.importNode(service_description, true));
94
95 //append site metadata
96 addSiteMetadata(page_response, userContext);
97 addInterfaceOptions(page_response);
98
99 // if rt=d, then we are just displaying the service
100 String request_type = (String) params.get(GSParams.REQUEST_TYPE);
101 if (request_type.equals("d"))
102 {
103 //return the page that we have so far
104 return page_response;
105 }
106
107 // get the node that the user has clicked on
108 String classifier_node = (String) params.get(CLASSIFIER_ARG);
109
110 // if the node is not defined, return the page that we have so far
111 if (classifier_node == null || classifier_node.equals(""))
112 {
113 return page_response;
114 }
115
116 // the id of the classifier is the top id of the selected node
117 String top_id = OID.getTop(classifier_node);
118 HashSet<String> doc_meta_names = new HashSet<String>();
119 HashSet<String> class_meta_names = new HashSet<String>();
120 // add in the defaults
121 doc_meta_names.add("Title");
122 class_meta_names.add("Title");
123
124 // add the format info into the response
125 Element format_elem = (Element) GSXML.getChildByTagName(format_response, GSXML.FORMAT_ELEM);
126 if (format_elem != null)
127 {
128 // find the one for the classifier we are in
129 Element this_format = GSXML.getNamedElement(format_elem, GSXML.CLASSIFIER_ELEM, GSXML.NAME_ATT, top_id);
130 if (this_format == null)
131 {
132 this_format = (Element) GSXML.getChildByTagName(format_elem, GSXML.DEFAULT_ELEM);
133 }
134
135 if (this_format != null)
136 {
137 Element global_format_elem = (Element) GSXML.getChildByTagName(format_response, GSXML.GLOBAL_FORMAT_ELEM);
138 if (global_format_elem != null)
139 {
140 GSXSLT.mergeFormatElements(this_format, global_format_elem, false);
141 }
142
143 Element new_format = GSXML.duplicateWithNewName(doc, this_format, GSXML.FORMAT_ELEM, false);
144 extractMetadataNames(new_format, doc_meta_names, class_meta_names);
145
146 Element extraMetaListElem = (Element) GSXML.getChildByTagName(request, GSXML.EXTRA_METADATA + GSXML.LIST_MODIFIER);
147 if (extraMetaListElem != null)
148 {
149 NodeList extraMetaList = extraMetaListElem.getElementsByTagName(GSXML.EXTRA_METADATA);
150 for (int i = 0; i < extraMetaList.getLength(); i++)
151 {
152 doc_meta_names.add(((Element) extraMetaList.item(i)).getAttribute(GSXML.NAME_ATT));
153 }
154 }
155
156 // set the format type
157 new_format.setAttribute(GSXML.TYPE_ATT, "browse");
158
159 page_response.appendChild(new_format);
160 }
161 }
162
163 // find out if this classifier is horizontal at top
164 Element class_list = (Element) GSXML.getChildByTagName(service_description, GSXML.CLASSIFIER_ELEM + GSXML.LIST_MODIFIER);
165 Element this_classifier = GSXML.getNamedElement(class_list, GSXML.CLASSIFIER_ELEM, GSXML.NAME_ATT, top_id);
166
167 // get the browse structure for the selected node
168 Element classify_message = doc.createElement(GSXML.MESSAGE_ELEM);
169 Element classify_request = GSXML.createBasicRequest(doc, GSXML.REQUEST_TYPE_PROCESS, to, userContext);
170 classify_message.appendChild(classify_request);
171
172 //Create a parameter list to specify the required structure information
173 // for now, always get ancestors and children
174 Element param_list = doc.createElement(GSXML.PARAM_ELEM + GSXML.LIST_MODIFIER);
175 classify_request.appendChild(param_list);
176 Element param = doc.createElement(GSXML.PARAM_ELEM);
177 param_list.appendChild(param);
178 param.setAttribute(GSXML.NAME_ATT, "structure");
179 param.setAttribute(GSXML.VALUE_ATT, "ancestors");
180 param = doc.createElement(GSXML.PARAM_ELEM);
181 param_list.appendChild(param);
182 param.setAttribute(GSXML.NAME_ATT, "structure");
183 param.setAttribute(GSXML.VALUE_ATT, "children");
184
185 if (get_descendants)
186 {
187 param = doc.createElement(GSXML.PARAM_ELEM);
188 param_list.appendChild(param);
189 param.setAttribute(GSXML.NAME_ATT, "structure");
190 param.setAttribute(GSXML.VALUE_ATT, "descendants");
191 }
192
193
194 // put the classifier node into a classifier node list
195 Element classifier_list = doc.createElement(GSXML.CLASS_NODE_ELEM + GSXML.LIST_MODIFIER);
196 Element classifier = doc.createElement(GSXML.CLASS_NODE_ELEM);
197 classifier.setAttribute(GSXML.NODE_ID_ATT, classifier_node);
198 classifier_list.appendChild(classifier);
199 classify_request.appendChild(classifier_list);
200
201 // process the request
202 Element classify_response = (Element) this.mr.process(classify_message);
203
204 String path = GSPath.appendLink(GSXML.RESPONSE_ELEM, GSXML.CLASS_NODE_ELEM + GSXML.LIST_MODIFIER);
205 Element class_node_list = (Element) GSXML.getNodeByPath(classify_response, path);
206
207 path = GSPath.appendLink(GSXML.CLASS_NODE_ELEM, GSXML.NODE_STRUCTURE_ELEM);
208 // assume that we always get back the top level CL1 node - this becomes the page_classifier node
209 path = GSPath.appendLink(path, GSXML.CLASS_NODE_ELEM);
210 Element cl_structure = (Element) GSXML.getNodeByPath(class_node_list, path);
211 if (cl_structure == null)
212 {
213 logger.error("classifier structure request returned no structure");
214 return page_response;
215 }
216
217 //If the user is viewing a horizontal classifier then we need to get extra information
218 if (cl_structure.getAttribute(GSXML.CHILD_TYPE_ATT).equals(GSXML.HLIST))
219 {
220 //If we have a horizontal classifier and we have had the top-level node requested (e.g. CL1, CL2 etc.)
221 //then we want to get the children of the first classifier node (e.g. the children of CL2.1)
222 if (OID.isTop(classifier_node))
223 {
224 boolean firstChildIsClassifierNode = false;
225 NodeList classifierChildrenNodes = GSXML.getChildrenByTagName(cl_structure, GSXML.CLASS_NODE_ELEM);
226 for (int i = 0; i < classifierChildrenNodes.getLength(); i++)
227 {
228 Element currentChild = (Element) classifierChildrenNodes.item(i);
229 if (currentChild.getAttribute(GSXML.NODE_ID_ATT).endsWith(".1"))
230 {
231 firstChildIsClassifierNode = true;
232 }
233 }
234
235 if (firstChildIsClassifierNode)
236 {
237 Element childStructure = getClassifierStructureFromID(doc, classifier_node + ".1", request, collection, service_name);
238
239 Element replacementElem = null;
240 NodeList childClassifierNodes = childStructure.getElementsByTagName(GSXML.CLASS_NODE_ELEM);
241 for (int i = 0; i < childClassifierNodes.getLength(); i++)
242 {
243 Element currentElem = (Element) childClassifierNodes.item(i);
244 if (currentElem.getAttribute(GSXML.NODE_ID_ATT).equals(classifier_node + ".1"))
245 {
246 replacementElem = currentElem;
247 break;
248 }
249 }
250
251 NodeList nodesToSearch = cl_structure.getElementsByTagName(GSXML.CLASS_NODE_ELEM);
252 for (int i = 0; i < nodesToSearch.getLength(); i++)
253 {
254 Element currentElem = (Element) nodesToSearch.item(i);
255 if (currentElem.getAttribute(GSXML.NODE_ID_ATT).equals(classifier_node + ".1"))
256 {
257 Element parent = (Element) currentElem.getParentNode();
258 parent.insertBefore(replacementElem, currentElem);
259 parent.removeChild(currentElem);
260 break;
261 }
262 }
263 }
264 }
265 //If we have a horizontal classifier and we have NOT had the top-level node requested then we need to
266 //make sure we get the full list of top-level children to display (e.g. if the user has requested
267 //CL2.1.1 we also need to make sure we have CL2.2, CL2.3, CL2.4 etc.)
268 else
269 {
270 Element childStructure = getClassifierStructureFromID(doc, OID.getTop(classifier_node), request, collection, service_name);
271
272 String[] idParts = classifier_node.split("\\.");
273 String idToSearchFor = idParts[0] + "." + idParts[1];
274
275 Element replacementElem = null;
276 NodeList childClassifierNodes = cl_structure.getElementsByTagName(GSXML.CLASS_NODE_ELEM);
277 for (int i = 0; i < childClassifierNodes.getLength(); i++)
278 {
279 Element currentElem = (Element) childClassifierNodes.item(i);
280 if (currentElem.getAttribute(GSXML.NODE_ID_ATT).equals(idToSearchFor))
281 {
282 replacementElem = currentElem;
283 break;
284 }
285 }
286
287 if (replacementElem != null)
288 {
289 NodeList nodesToSearch = childStructure.getElementsByTagName(GSXML.CLASS_NODE_ELEM);
290 for (int i = 0; i < nodesToSearch.getLength(); i++)
291 {
292 Element currentElem = (Element) nodesToSearch.item(i);
293 if (currentElem.getAttribute(GSXML.NODE_ID_ATT).equals(idToSearchFor))
294 {
295 Element parent = (Element) currentElem.getParentNode();
296 parent.insertBefore(replacementElem, currentElem);
297 parent.removeChild(currentElem);
298 break;
299 }
300 }
301
302 cl_structure = childStructure;
303 }
304 }
305 }
306
307 Element page_classifier = null;
308 // add the single classifier node as the page classifier
309 page_classifier = GSXML.duplicateWithNewName(doc, cl_structure, GSXML.CLASSIFIER_ELEM, true);
310 page_response.appendChild(page_classifier);
311 page_classifier.setAttribute(GSXML.NAME_ATT, top_id);
312
313 // get the metadata for each classifier node,
314 // then for each document node
315
316 Element metadata_message = doc.createElement(GSXML.MESSAGE_ELEM);
317
318 boolean did_classifier = false;
319 boolean did_documents = false;
320
321 // if there are classifier nodes
322 // create a metadata request for the classifier, and add it to
323 // the the message
324 NodeList cl_nodes = page_classifier.getElementsByTagName(GSXML.CLASS_NODE_ELEM);
325
326 if (cl_nodes.getLength() > 0)
327 {
328 did_classifier = true;
329 Element cl_meta_request = GSXML.createBasicRequest(doc, GSXML.REQUEST_TYPE_PROCESS, to + "MetadataRetrieve", userContext);
330 metadata_message.appendChild(cl_meta_request);
331
332 Element new_cl_nodes_list = doc.createElement(GSXML.CLASS_NODE_ELEM + GSXML.LIST_MODIFIER);
333 cl_meta_request.appendChild(new_cl_nodes_list);
334
335 for (int c = 0; c < cl_nodes.getLength(); c++)
336 {
337
338 Element cl = doc.createElement(GSXML.CLASS_NODE_ELEM);
339 cl.setAttribute(GSXML.NODE_ID_ATT, ((Element) cl_nodes.item(c)).getAttribute(GSXML.NODE_ID_ATT));
340 new_cl_nodes_list.appendChild(cl);
341 }
342
343 // create and add in the param list - for now get all the metadata
344 // should be based on info sent in from the recept, and the
345 // format stuff
346 Element cl_param_list = createMetadataParamList(doc,class_meta_names);
347 cl_meta_request.appendChild(cl_param_list);
348
349 }
350
351 // if there are document nodes in the classification (happens
352 // sometimes), create a second request for document metadata and
353 // append to the message
354 NodeList doc_nodes = page_classifier.getElementsByTagName(GSXML.DOC_NODE_ELEM);
355 if (doc_nodes.getLength() > 0)
356 {
357 did_documents = true;
358 Element doc_meta_request = GSXML.createBasicRequest(doc, GSXML.REQUEST_TYPE_PROCESS, GSPath.appendLink(collection, "DocumentMetadataRetrieve"), userContext);
359 metadata_message.appendChild(doc_meta_request);
360
361 Element doc_list = doc.createElement(GSXML.DOC_NODE_ELEM + GSXML.LIST_MODIFIER);
362 doc_meta_request.appendChild(doc_list);
363
364 for (int c = 0; c < doc_nodes.getLength(); c++)
365 {
366
367 Element d = doc.createElement(GSXML.DOC_NODE_ELEM);
368 d.setAttribute(GSXML.NODE_ID_ATT, ((Element) doc_nodes.item(c)).getAttribute(GSXML.NODE_ID_ATT));
369 doc_list.appendChild(d);
370 }
371
372 // create and add in the param list - add all for now
373 Element doc_param_list = createMetadataParamList(doc,doc_meta_names);
374 doc_meta_request.appendChild(doc_param_list);
375
376 }
377
378 // process the metadata requests
379 Element metadata_response = (Element) this.mr.process(metadata_message);
380 if (did_classifier)
381 {
382 // the classifier one will be the first response
383 // add the metadata lists for each node back into the
384 // page_classifier nodes
385 path = GSPath.appendLink(GSXML.RESPONSE_ELEM, GSXML.CLASS_NODE_ELEM + GSXML.LIST_MODIFIER);
386 Node pathNode = GSXML.getNodeByPath(metadata_response, path);
387 if (pathNode == null)
388 {
389 return page_response;
390 }
391 //NodeList meta_response_cls = (Element)pathNode.getChildNodes(); // can't handle empty elements from converting formatted strings (with empty newlines) into XML
392 NodeList meta_response_cls = ((Element) pathNode).getElementsByTagName(GSXML.CLASS_NODE_ELEM);
393 for (int i = 0; i < cl_nodes.getLength(); i++)
394 {
395 GSXML.mergeMetadataLists(cl_nodes.item(i), meta_response_cls.item(i));
396 }
397 }
398
399 if (did_documents)
400 {
401 NodeList meta_response_docs = null;
402 if (!did_classifier)
403 {
404 // its the first response
405 path = GSPath.appendLink(GSXML.RESPONSE_ELEM, GSXML.DOC_NODE_ELEM + GSXML.LIST_MODIFIER);
406 Node pathNode = GSXML.getNodeByPath(metadata_response, path);
407 if (pathNode == null)
408 {
409 return page_response;
410 }
411
412 meta_response_docs = pathNode.getChildNodes();
413
414 }
415 else
416 { // its the second response
417 Node nodes = GSXML.getChildByTagName(metadata_response.getElementsByTagName(GSXML.RESPONSE_ELEM).item(1), GSXML.DOC_NODE_ELEM + GSXML.LIST_MODIFIER);
418 if (nodes == null)
419 {
420 return page_response;
421 }
422 meta_response_docs = nodes.getChildNodes();
423 }
424
425 for (int i = 0; i < doc_nodes.getLength(); i++)
426 {
427 GSXML.mergeMetadataLists(doc_nodes.item(i), meta_response_docs.item(i));
428 }
429 }
430
431 logger.debug("(GS2BrowseAction) Page:\n" + this.converter.getPrettyString(page_response));
432 return page_response;
433 }
434
435 private Element getClassifierStructureFromID(Document doc, String id, Element request, String collection, String service_name)
436 {
437 UserContext userContext = new UserContext(request);
438 String to = GSPath.appendLink(collection, service_name);
439
440 Element firstClassifierNodeChildrenMessage = doc.createElement(GSXML.MESSAGE_ELEM);
441 Element firstClassifierNodeChildrenRequest = GSXML.createBasicRequest(doc, GSXML.REQUEST_TYPE_PROCESS, to, userContext);
442 firstClassifierNodeChildrenMessage.appendChild(firstClassifierNodeChildrenRequest);
443
444 Element paramList = doc.createElement(GSXML.PARAM_ELEM + GSXML.LIST_MODIFIER);
445 firstClassifierNodeChildrenRequest.appendChild(paramList);
446
447 Element ancestorParam = doc.createElement(GSXML.PARAM_ELEM);
448 paramList.appendChild(ancestorParam);
449 ancestorParam.setAttribute(GSXML.NAME_ATT, "structure");
450 ancestorParam.setAttribute(GSXML.VALUE_ATT, "ancestors");
451
452 Element childrenParam = doc.createElement(GSXML.PARAM_ELEM);
453 paramList.appendChild(childrenParam);
454 childrenParam.setAttribute(GSXML.NAME_ATT, "structure");
455 childrenParam.setAttribute(GSXML.VALUE_ATT, "children");
456
457 Element classifierToGetList = doc.createElement(GSXML.CLASS_NODE_ELEM + GSXML.LIST_MODIFIER);
458 Element classifierToGet = doc.createElement(GSXML.CLASS_NODE_ELEM);
459 classifierToGet.setAttribute(GSXML.NODE_ID_ATT, id);
460 classifierToGetList.appendChild(classifierToGet);
461 firstClassifierNodeChildrenRequest.appendChild(classifierToGetList);
462
463 Element firstClassifierNodeChildrenResponse = (Element) this.mr.process(firstClassifierNodeChildrenMessage);
464
465 String nsPath = GSPath.appendLink(GSXML.RESPONSE_ELEM, GSXML.CLASS_NODE_ELEM + GSXML.LIST_MODIFIER);
466 Element topClassifierNode = (Element) GSXML.getNodeByPath(firstClassifierNodeChildrenResponse, nsPath);
467 nsPath = GSPath.appendLink(GSXML.CLASS_NODE_ELEM, GSXML.NODE_STRUCTURE_ELEM);
468 nsPath = GSPath.appendLink(nsPath, GSXML.CLASS_NODE_ELEM);
469 Element childStructure = (Element) GSXML.getNodeByPath(topClassifierNode, nsPath);
470
471 return childStructure;
472 }
473
474 protected void extractMetadataNames(Element new_format, HashSet<String> doc_meta_names, HashSet<String> class_meta_names)
475 {
476 NodeList templates = new_format.getElementsByTagNameNS(GSXML.GSF_NAMESPACE, "template");
477 for (int i = 0; i < templates.getLength(); i++)
478 {
479 Element template = (Element) templates.item(i);
480 String match = template.getAttribute(GSXML.MATCH_ATT);
481 String name = template.getAttribute(GSXML.NAME_ATT);
482 if (match != null && match.length() > 0)
483 {
484 if (match.startsWith("documentNode"))
485 {
486 getRequiredMetadataNames(template, doc_meta_names);
487 }
488 else if (match.startsWith("classifierNode")) // not match.equals, as we want to match nodes like: classifierNode[@classifierStyle = 'VList']
489 {
490 getRequiredMetadataNames(template, class_meta_names);
491 }
492 }
493 else
494 {
495 getRequiredMetadataNames(template, doc_meta_names);
496 getRequiredMetadataNames(template, class_meta_names);
497 }
498 }
499 }
500
501}
Note: See TracBrowser for help on using the repository browser.