source: greenstone3/trunk/src/java/org/greenstone/gsdl3/core/OAIReceptionist.java@ 16688

Last change on this file since 16688 was 16688, checked in by davidb, 16 years ago

Changed 'Element process(Element)' in ModuleInterface to 'Node process(Node)'. After some deliberation is was decided this is a more useful (generic) layer of the DOM to pass information around in. Helps with the DocType problem when producing XSL Transformed pages, for example. When this was an Element, it would loose track of its DocType. Supporting method provided in XMLConverter 'Element nodeToElement(Node)' which checks a nodes docType and casts to Element if appropriate, or if a Document, typecasts to that and then extracts the top-level Element. With this fundamental change in ModuleInterface, around 20 files needed to be updated (Actions, Services, etc) that build on top of 'process()' to reflect this change, and use nodeToElement where necessary.

File size: 40.9 KB
Line 
1package org.greenstone.gsdl3.core;
2
3import org.greenstone.gsdl3.util.*;
4import org.greenstone.gsdl3.action.*;
5// XML classes
6import org.w3c.dom.Node;
7import org.w3c.dom.NodeList;
8import org.w3c.dom.Document;
9import org.w3c.dom.Element;
10
11// other java classes
12import java.io.File;
13import java.util.*;
14
15import org.apache.log4j.*;
16
17/** a Receptionist, used for oai metadata response xml generation.
18 * This receptionist talks to the message router directly,
19 * instead of via any action, hence no action map is needed.
20 * @see the basic Receptionist
21 */
22public class OAIReceptionist implements ModuleInterface {
23
24 static Logger logger = Logger.getLogger(org.greenstone.gsdl3.core.OAIReceptionist.class.getName());
25
26 /** Instead of a config_params object, only a site_name is needed by oai receptionist. */
27 protected String site_name = null;
28 /** container Document to create XML Nodes for requests sent to message router
29 * Not used for response
30 */
31 protected Document doc=null;
32
33 /** a converter class to parse XML and create Docs */
34 protected XMLConverter converter=null;
35
36 /** the configure file of this receptionist passed from the oai servlet. */
37 protected Element oai_config = null;
38
39 /** contained in the OAIConfig.xml deciding whether the resumptionToken should be in use */
40 protected int resume_after = -1 ;
41
42 /** the message router that the Receptionist and Actions will talk to */
43 protected ModuleInterface mr = null;
44
45 public OAIReceptionist() {
46 this.converter = new XMLConverter();
47 this.doc = this.converter.newDOM();
48
49 }
50
51 public void cleanUp() {}
52
53 public void setSiteName(String site_name) {
54 this.site_name = site_name;
55 }
56 /** sets the message router - it should already be created and
57 * configured in the init() of a servlet (OAIServer, for example) before being passed to the receptionist*/
58 public void setMessageRouter(ModuleInterface mr) {
59 this.mr = mr;
60 }
61
62 /** configures the receptionist */
63 public boolean configure(Element config) {
64
65 if (this.mr==null) {
66 logger.error(" message routers must be set before calling oai configure");
67 return false;
68 }
69 if (config == null) {
70 logger.error(" oai configure file is null");
71 return false;
72 }
73 oai_config = config;
74 resume_after = getResumeAfter();
75
76 //clear out expired resumption tokens stored in OAIResumptionToken.xml
77 OAIXML.init();
78 OAIXML.clearExpiredTokens();
79
80 return true;
81 }
82 /** process using strings - just calls process using Elements */
83 public String process(String xml_in) {
84
85 Node message_node = this.converter.getDOM(xml_in);
86 Node page = process(message_node);
87 return this.converter.getString(page);
88 }
89
90 //Compose a message element used to send back to the OAIServer servlet.
91 //This method is only used within OAIReceptionist
92 private Element getMessage(Element e) {
93 Element msg = OAIXML.createElement(OAIXML.MESSAGE);
94 msg.appendChild(OAIXML.getResponse(e));
95 return msg;
96 }
97 /** process - produce xml data in response to a request
98 * if something goes wrong, it returns null -
99 */
100 public Node process(Node message_node) {
101 logger.error("OAIReceptionist received request");
102
103 Element message = this.converter.nodeToElement(message_node);
104 logger.error(this.converter.getString(message));
105
106 // check that its a correct message tag
107 if (!message.getTagName().equals(GSXML.MESSAGE_ELEM)) {
108 logger.error(" Invalid message. GSDL message should start with <"+GSXML.MESSAGE_ELEM+">, instead it starts with:"+message.getTagName()+".");
109 return getMessage(OAIXML.createErrorElement(OAIXML.BAD_ARGUMENT, ""));
110 }
111
112 // get the request out of the message - assume that there is only one
113 Element request = (Element)GSXML.getChildByTagName(message, GSXML.REQUEST_ELEM);
114 if (request == null) {
115 logger.error(" message had no request!");
116 return getMessage(OAIXML.createErrorElement(OAIXML.BAD_ARGUMENT, ""));
117 }
118 //At this stage, the value of 'to' attribute of the request must be the 'verb'
119 //The only thing that the oai receptionist can be sure is that these verbs are valid, nothing else.
120 String verb = request.getAttribute(GSXML.TO_ATT);
121 if (verb.equals(OAIXML.IDENTIFY)) {
122 return doIdentify();
123 }
124 if (verb.equals(OAIXML.LIST_METADATA_FORMATS)) {
125 return doListMetadataFormats(message);
126 }
127 if (verb.equals(OAIXML.LIST_SETS)) {
128 return doListSets(message);
129 }
130 if (verb.equals(OAIXML.GET_RECORD)) {
131 return doGetRecord(message);
132 }
133 if (verb.equals(OAIXML.LIST_IDENTIFIERS)) {
134 return doListIdentifiers(message);
135 }
136 if (verb.equals(OAIXML.LIST_RECORDS)) {
137 return doListRecords(message);
138 }
139 return getMessage(OAIXML.createErrorElement("Unexpected things happened", ""));
140
141 }
142 /** send a request to the message router asking for a list of collections that support oai
143 * The type attribute must be changed from 'oaiService' to 'oaiSetList'
144 */
145 private NodeList getOAICollectionList() {
146 Element message = this.doc.createElement(OAIXML.MESSAGE);
147 Element request = this.doc.createElement(OAIXML.REQUEST);
148 message.appendChild(request);
149 request.setAttribute(OAIXML.TYPE, OAIXML.OAI_SET_LIST);
150 request.setAttribute(OAIXML.TO, "");
151 Node msg_node = mr.process(message);
152
153 if (msg_node == null) {
154 logger.error("returned msg_node from mr is null");
155 return null;
156 }
157 Element resp = (Element)GSXML.getChildByTagName(msg_node, OAIXML.RESPONSE);
158 Element coll_list = (Element)GSXML.getChildByTagName(resp, OAIXML.COLLECTION_LIST);
159 if (coll_list == null) {
160 logger.error("coll_list is null");
161 return null;
162 }
163 //logger.info(GSXML.xmlNodeToString(coll_list));
164 NodeList list = coll_list.getElementsByTagName(OAIXML.COLLECTION);
165 int length = list.getLength();
166 if (length == 0) {
167 logger.error("length is 0");
168 return null;
169 }
170 return list;
171 }
172 /**Exclusively called by doListSets()*/
173 private void getSets(Element list_sets_elem, NodeList oai_coll, int start_point, int end_point) {
174 for (int i=start_point; i<end_point; i++) {
175 String coll_spec = ((Element)oai_coll.item(i)).getAttribute(OAIXML.NAME);
176 String coll_name = coll_spec.substring(coll_spec.indexOf(":") + 1);
177 Element set = OAIXML.createElement(OAIXML.SET);
178 Element set_spec = OAIXML.createElement(OAIXML.SET_SPEC);
179 GSXML.setNodeText(set_spec, coll_spec);
180 set.appendChild(set_spec);
181 Element set_name = OAIXML.createElement(OAIXML.SET_NAME);
182 GSXML.setNodeText(set_name, coll_name);
183 set.appendChild(set_name);
184 list_sets_elem.appendChild(set);
185 }
186 }
187 private int getResumeAfter() {
188 Element resume_after = (Element)GSXML.getChildByTagName(oai_config, OAIXML.RESUME_AFTER);
189 if(resume_after != null) return Integer.parseInt(GSXML.getNodeText(resume_after));
190 return -1;
191 }
192 /** method to compose a set element
193 */
194 private Element doListSets(Element msg){
195 logger.info("");
196 // option: resumptionToken
197 // exceptions: badArgument, badResumptionToken, noSetHierarchy
198 Element list_sets_elem = OAIXML.createElement(OAIXML.LIST_SETS);
199
200 //ask the message router for a list of oai collections
201 NodeList oai_coll = getOAICollectionList();
202 int oai_coll_size = oai_coll.getLength();
203 if (oai_coll_size == 0) {
204 return getMessage(list_sets_elem);
205 }
206
207 Element req = (Element)GSXML.getChildByTagName(msg, GSXML.REQUEST_ELEM);
208 if (req == null) {
209 logger.error("req is null");
210 return null;
211 }
212 //params list only contains the parameters other than the verb
213 NodeList params = GSXML.getChildrenByTagName(req, OAIXML.PARAM);
214 Element param = null;
215 int smaller = (oai_coll_size>resume_after)? resume_after : oai_coll_size;
216 if (params.getLength() > 1) {
217 //Bad argument. Can't be more than one parameters for ListMetadataFormats verb
218 return getMessage(OAIXML.createErrorElement(OAIXML.BAD_ARGUMENT, ""));
219 }
220 if(params.getLength() == 0) {
221 //this is requesting a list of sets in the whole repository
222 /** there is no resumeptionToken in the request, we check whether we need
223 * to send out resumeptionToken by comparing the total number of sets in this
224 * repository and the specified value of resumeAfter
225 */
226 if(resume_after < 0 || oai_coll_size <= resume_after) {
227 //send the whole list of records
228 //all data are sent on the first request. Therefore there should be
229 //no resumeptionToken stored in OAIConfig.xml.
230 //As long as the verb is 'ListSets', we ignore the rest of the parameters
231 getSets(list_sets_elem, oai_coll, 0, oai_coll_size);
232 return getMessage(list_sets_elem);
233 }
234
235 //append required sets to list_sets_elem (may be a complete or incomplete list)
236 getSets(list_sets_elem, oai_coll, 0, smaller);
237
238 if(oai_coll_size > resume_after) {
239 //An incomplete list is sent; append a resumptionToken element
240 Element token = createResumptionTokenElement(oai_coll_size, 0, resume_after, true);
241 //store this token
242 OAIXML.addToken(token);
243
244 list_sets_elem.appendChild(token);
245 }
246
247 return getMessage(list_sets_elem);
248 }
249
250 // The url should contain only one param called resumptionToken
251 // This is requesting a subsequent part of a list, following a previously sent incomplete list
252 param = (Element)params.item(0);
253 String param_name = param.getAttribute(OAIXML.NAME);
254 if (!param_name.equals(OAIXML.RESUMPTION_TOKEN)) {
255 //Bad argument
256 return getMessage(OAIXML.createErrorElement(OAIXML.BAD_ARGUMENT, ""));
257 }
258 //get the token
259 String token = param.getAttribute(OAIXML.VALUE);
260 //validate the token string (the string has already been decoded in OAIServer, e.g.,
261 // replace %3A with ':')
262 if(OAIXML.containsToken(token) == false) {
263 return getMessage(OAIXML.createErrorElement(OAIXML.BAD_RESUMPTION_TOKEN, ""));
264 }
265 //take out the cursor value, which is the size of previously sent list
266 int index = token.indexOf(":");
267 int cursor = Integer.parseInt(token.substring(index + 1));
268 Element token_elem = null;
269
270 // are we sending the final part of a complete list?
271 if(cursor + resume_after >= oai_coll_size) {
272 //Yes, we are.
273 //append required sets to list_sets_elem (list is complete)
274 getSets(list_sets_elem, oai_coll, cursor, oai_coll_size);
275 //An incomplete list is sent; append a resumptionToken element
276 token_elem = createResumptionTokenElement(oai_coll_size, cursor, -1, false);
277 list_sets_elem.appendChild(token_elem);
278 } else {
279 //No, we are not.
280 //append required sets to list_sets_elem (list is incomplete)
281 getSets(list_sets_elem, oai_coll, cursor, cursor + resume_after);
282 token_elem = createResumptionTokenElement(oai_coll_size, cursor, cursor + resume_after, true);
283 //store this token
284 OAIXML.addToken(token_elem);
285 list_sets_elem.appendChild(token_elem);
286 }
287 return getMessage(list_sets_elem);
288 }
289 private Element createResumptionTokenElement(int total_size, int cursor, int so_far_sent, boolean set_expiration) {
290 Element token = OAIXML.createElement(OAIXML.RESUMPTION_TOKEN);
291 token.setAttribute(OAIXML.COMPLETE_LIST_SIZE, "" + total_size);
292 token.setAttribute(OAIXML.CURSOR, "" + cursor);
293
294 if(set_expiration) {
295 /** read the resumptionTokenExpiration element in OAIConfig.xml and get the specified time value
296 * Use the time value plus the current system time to get the expiration date string.
297 */
298 String expiration_date = OAIXML.getTime(System.currentTimeMillis() + OAIXML.getTokenExpiration());
299 token.setAttribute(OAIXML.EXPIRATION_DATE, expiration_date);
300 }
301
302 if(so_far_sent > 0) {
303 //the format of resumptionToken is not defined by the OAI-PMH and should be
304 //considered opaque by the harvester (in other words, strictly follow what the
305 //data provider has to offer
306 //Here, we make use of the uniqueness of the system time
307 GSXML.setNodeText(token, OAIXML.GS3OAI + System.currentTimeMillis() + ":" + so_far_sent);
308 }
309 return token;
310 }
311 /** if the param_map contains strings other than those in valid_strs, return false;
312 * otherwise true.
313 */
314 private boolean isValidParam(HashMap param_map, HashSet valid_strs) {
315 ArrayList param_list = new ArrayList(param_map.keySet());
316 for(int i=0; i<param_list.size(); i++) {
317 if (valid_strs.contains((String)param_list.get(i)) == false) {
318 return false;
319 }
320 }
321 return true;
322 }
323 private Element doListIdentifiers(Element msg) {
324 // option: from, until, set, metadataPrefix, resumptionToken
325 // exceptions: badArgument, badResumptionToken, cannotDisseminateFormat, noRecordMatch, and noSetHierarchy
326 HashSet valid_strs = new HashSet();
327 valid_strs.add(OAIXML.FROM);
328 valid_strs.add(OAIXML.UNTIL);
329 valid_strs.add(OAIXML.SET);
330 valid_strs.add(OAIXML.METADATA_PREFIX);
331 valid_strs.add(OAIXML.RESUMPTION_TOKEN);
332
333 Element list_identifiers = OAIXML.createElement(OAIXML.LIST_IDENTIFIERS);
334 Element req = (Element)GSXML.getChildByTagName(msg, GSXML.REQUEST_ELEM);
335 if (req == null) { logger.error("req is null"); return null; }
336 NodeList params = GSXML.getChildrenByTagName(req, OAIXML.PARAM);
337 String coll_name = "";
338 String token = "";
339
340 HashMap param_map = OAIXML.getParamMap(params);
341 if (!isValidParam(param_map, valid_strs) ||
342 !param_map.containsKey(OAIXML.METADATA_PREFIX)) {
343 logger.error("contains invalid params or no metadataPrefix");
344 return getMessage(OAIXML.createErrorElement(OAIXML.BAD_ARGUMENT, ""));
345 }
346 //ask the message router for a list of oai collections
347 NodeList oai_coll = getOAICollectionList();
348 int oai_coll_size = oai_coll.getLength();
349 if (oai_coll_size == 0) {
350 logger.info("returned oai collection list is empty");
351 return getMessage(OAIXML.createErrorElement(OAIXML.NO_RECORDS_MATCH, ""));
352 }
353 //Now that we got a prefix, check and see if it's supported by this repository
354 String prefix_value = (String)param_map.get(OAIXML.METADATA_PREFIX);
355 if (containsMetadataPrefix(prefix_value) == false) {
356 logger.error("requested prefix is not found in OAIConfig.xml");
357 return getMessage(OAIXML.createErrorElement(OAIXML.CANNOT_DISSEMINATE_FORMAT, ""));
358 }
359
360 //Now we check if the optional argument 'set' has been specified in the params; if so,
361 //whether the specified setSpec is supported by this repository
362 boolean request_set = param_map.containsKey(OAIXML.SET);
363 if(request_set == true) {
364 boolean set_supported = false;
365 String set_spec_str = (String)param_map.get(OAIXML.SET);
366 // get the collection name
367 //if setSpec is supported by this repository, it must be in the form: site_name:coll_name
368 String[] strs = splitSetSpec(set_spec_str);
369 coll_name = strs[1];
370
371 for(int i=0; i<oai_coll_size; i++) {
372 if(set_spec_str.equals(((Element)oai_coll.item(i)).getAttribute(OAIXML.NAME))) {
373 set_supported = true;
374 }
375 }
376 if(set_supported == false) {
377 logger.error("requested set is not found in this repository");
378 return getMessage(OAIXML.createErrorElement(OAIXML.BAD_ARGUMENT, ""));
379 }
380 }
381
382 //Is there a resumptionToken included which is requesting an incomplete list?
383 if(param_map.containsKey(OAIXML.RESUMPTION_TOKEN)) {
384 // validate resumptionToken
385 token = (String)param_map.get(OAIXML.RESUMPTION_TOKEN);
386 logger.info("has resumptionToken" + token);
387 if(OAIXML.containsToken(token) == false) {
388 return getMessage(OAIXML.createErrorElement(OAIXML.BAD_RESUMPTION_TOKEN, ""));
389 }
390 }
391
392 //Now that all validation has been done, I hope, we can send request to the message router
393 Element result = null;
394 String verb = req.getAttribute(OAIXML.TO);
395 NodeList param_list = req.getElementsByTagName(OAIXML.PARAM);
396 ArrayList retain_param_list = new ArrayList();
397 for (int j=0; j<param_list.getLength(); j++) {
398 Element e = OAIXML.duplicateElement(msg.getOwnerDocument(), (Element)param_list.item(j), true);
399 retain_param_list.add(e);
400 }
401
402 //re-organize the request element
403 // reset the 'to' attribute
404 if (request_set == false) {
405 logger.info("requesting identifiers of all collections");
406 for(int i=0; i<oai_coll_size; i++) {
407 if(req == null) {
408 req = msg.getOwnerDocument().createElement(GSXML.REQUEST_ELEM);
409 msg.appendChild(req);
410 for (int j=0; j<retain_param_list.size(); j++) {
411 req.appendChild((Element)retain_param_list.get(j));
412 }
413 }
414 String full_name = ((Element)oai_coll.item(i)).getAttribute(OAIXML.NAME);
415 coll_name = full_name.substring(full_name.indexOf(":") + 1);
416 req.setAttribute(OAIXML.TO, coll_name + "/" + verb);
417 Node n = mr.process(msg);
418 Element e = converter.nodeToElement(n);
419 result = collectAll(result, e, verb, OAIXML.HEADER);
420
421 //clear the content of the old request element
422 msg.removeChild(req);
423 req = null;
424 }
425 } else {
426 req.setAttribute(OAIXML.TO, coll_name + "/" + verb);
427 Node result_node = mr.process(msg);
428 result = converter.nodeToElement(result_node);
429 }
430
431 if (result == null) {
432 logger.info("message router returns null");
433 return getMessage(OAIXML.createErrorElement("Internal service returns null", ""));
434 }
435 Element res = (Element)GSXML.getChildByTagName(result, OAIXML.RESPONSE);
436 if(res == null) {
437 logger.info("response element in xml_result is null");
438 return getMessage(OAIXML.createErrorElement("Internal service returns null", ""));
439 }
440 NodeList header_list = res.getElementsByTagName(OAIXML.HEADER);
441 int num_headers = header_list.getLength();
442 if(num_headers == 0) {
443 logger.info("message router returns 0 headers.");
444 return getMessage(OAIXML.createErrorElement(OAIXML.NO_RECORDS_MATCH, ""));
445 }
446
447 //The request coming in does not contain a token, but we have to check the resume_after value and see if we need to issue a resumption token and
448 // save the token as well.
449 if (token.equals("") == true) {
450 if(resume_after < 0 || num_headers <= resume_after) {
451 //send the whole list of records
452 return result;
453 }
454
455 //append required number of records (may be a complete or incomplete list)
456 getRecords(list_identifiers, header_list, 0, resume_after);
457 //An incomplete list is sent; append a resumptionToken element
458 Element token_elem = createResumptionTokenElement(num_headers, 0, resume_after, true);
459 //store this token
460 OAIXML.addToken(token_elem);
461
462 list_identifiers.appendChild(token_elem);
463 return getMessage(list_identifiers);
464 }
465
466 if (token.equals("") == false) {
467 //get an appropriate number of records (partial list) according to the token
468 //take out the cursor value, which is the size of previously sent list
469 int index = token.indexOf(":");
470 int cursor = Integer.parseInt(token.substring(index + 1));
471 Element token_elem = null;
472
473 // are we sending the final part of a complete list?
474 if(cursor + resume_after >= num_headers) {
475 //Yes, we are.
476 //append required records to list_records (list is complete)
477 getRecords(list_identifiers, header_list, cursor, num_headers);
478 //An incomplete list is sent; append a resumptionToken element
479 token_elem = createResumptionTokenElement(num_headers, cursor, -1, false);
480 list_identifiers.appendChild(token_elem);
481 } else {
482 //No, we are not.
483 //append required records to list_records (list is incomplete)
484 getRecords(list_identifiers, header_list, cursor, cursor + resume_after);
485 token_elem = createResumptionTokenElement(num_headers, cursor, cursor + resume_after, true);
486 //store this token
487 OAIXML.addToken(token_elem);
488 list_identifiers.appendChild(token_elem);
489 }
490
491 return getMessage(list_identifiers);
492 }//end of if(!token.equals(""))
493
494 return result;
495 }
496 private Element doListRecords(Element msg){
497 logger.info("");
498 // option: from, until, set, metadataPrefix, and resumptionToken
499 // exceptions: badArgument, badResumptionToken, cannotDisseminateFormat, noRecordMatch, and noSetHierarchy
500 HashSet valid_strs = new HashSet();
501 valid_strs.add(OAIXML.FROM);
502 valid_strs.add(OAIXML.UNTIL);
503 valid_strs.add(OAIXML.SET);
504 valid_strs.add(OAIXML.METADATA_PREFIX);
505 valid_strs.add(OAIXML.RESUMPTION_TOKEN);
506
507 Element list_records = OAIXML.createElement(OAIXML.LIST_RECORDS);
508 Element req = (Element)GSXML.getChildByTagName(msg, GSXML.REQUEST_ELEM);
509 if (req == null) { logger.error("req is null"); return null; }
510 NodeList params = GSXML.getChildrenByTagName(req, OAIXML.PARAM);
511
512 String coll_name = "";
513 String token = "";
514
515 if(params.getLength() == 0) {
516 logger.error("must at least have the metadataPrefix parameter, can't be none");
517 return getMessage(OAIXML.createErrorElement(OAIXML.BAD_ARGUMENT, ""));
518 }
519
520 HashMap param_map = OAIXML.getParamMap(params);
521 if (!isValidParam(param_map, valid_strs) ||
522 !param_map.containsKey(OAIXML.METADATA_PREFIX)) {
523 // it must have a metadataPrefix
524 /** Here I disagree with the OAI specification: even if a resumptionToken is
525 * included in the request, the metadataPrefix is a must argument. Otherwise
526 * how would we know what metadataPrefix the harvester requested in his last request?
527 */
528 logger.error("no metadataPrefix");
529 return getMessage(OAIXML.createErrorElement(OAIXML.BAD_ARGUMENT, ""));
530 }
531
532 //ask the message router for a list of oai collections
533 NodeList oai_coll = getOAICollectionList();
534 int oai_coll_size = oai_coll.getLength();
535 if (oai_coll_size == 0) {
536 logger.info("returned oai collection list is empty");
537 return getMessage(OAIXML.createErrorElement(OAIXML.NO_RECORDS_MATCH, ""));
538 }
539
540 //Now that we got a prefix, check and see if it's supported by this repository
541 String prefix_value = (String)param_map.get(OAIXML.METADATA_PREFIX);
542 if (containsMetadataPrefix(prefix_value) == false) {
543 logger.error("requested prefix is not found in OAIConfig.xml");
544 return getMessage(OAIXML.createErrorElement(OAIXML.CANNOT_DISSEMINATE_FORMAT, ""));
545 }
546
547 //Now we check if the optional argument 'set' has been specified in the params; if so,
548 //whether the specified setSpec is supported by this repository
549 boolean request_set = param_map.containsKey(OAIXML.SET);
550 if(request_set == true) {
551 boolean set_supported = false;
552 String set_spec_str = (String)param_map.get(OAIXML.SET);
553 // get the collection name
554 //if setSpec is supported by this repository, it must be in the form: site_name:coll_name
555 String[] strs = splitSetSpec(set_spec_str);
556// name_of_site = strs[0];
557 coll_name = strs[1];
558 //logger.info("param contains set: "+coll_name);
559
560 for(int i=0; i<oai_coll_size; i++) {
561 if(set_spec_str.equals(((Element)oai_coll.item(i)).getAttribute(OAIXML.NAME))) {
562 set_supported = true;
563 }
564 }
565 if(set_supported == false) {
566 logger.error("requested set is not found in this repository");
567 return getMessage(OAIXML.createErrorElement(OAIXML.BAD_ARGUMENT, ""));
568 }
569 }
570
571 //Is there a resumptionToken included which is requesting an incomplete list?
572 if(param_map.containsKey(OAIXML.RESUMPTION_TOKEN)) {
573 // validate resumptionToken
574 //if (the token value is not found in the token xml file) {
575 // return getMessage(OAIXML.createErrorElement(OAIXML.BAD_RESUMPTION_TOKEN, ""));
576 //} else {
577 // use the request to get a complete list of records from the message router
578 // and issue the subsequent part of that complete list according to the token.
579 // store a new token if necessary.
580 //}
581 token = (String)param_map.get(OAIXML.RESUMPTION_TOKEN);
582 logger.info("has resumptionToken" + token);
583 if(OAIXML.containsToken(token) == false) {
584 return getMessage(OAIXML.createErrorElement(OAIXML.BAD_RESUMPTION_TOKEN, ""));
585 }
586 }
587 //Now that all validation has been done, I hope, we can send request to the message router
588 Element result = null;
589 String verb = req.getAttribute(OAIXML.TO);
590 NodeList param_list = req.getElementsByTagName(OAIXML.PARAM);
591 ArrayList retain_param_list = new ArrayList();
592 for (int j=0; j<param_list.getLength(); j++) {
593 Element e = OAIXML.duplicateElement(msg.getOwnerDocument(), (Element)param_list.item(j), true);
594 retain_param_list.add(e);
595 }
596
597 //re-organize the request element
598 // reset the 'to' attribute
599 if (request_set == false) {
600 //coll_name could be "", which means it's requesting all records of all collections
601 //we send a request to each collection asking for its records
602 for(int i=0; i<oai_coll_size; i++) {
603 if(req == null) {
604 req = msg.getOwnerDocument().createElement(GSXML.REQUEST_ELEM);
605 msg.appendChild(req);
606 for (int j=0; j<retain_param_list.size(); j++) {
607 req.appendChild((Element)retain_param_list.get(j));
608 }
609 }
610 String full_name = ((Element)oai_coll.item(i)).getAttribute(OAIXML.NAME);
611 coll_name = full_name.substring(full_name.indexOf(":") + 1);
612 req.setAttribute(OAIXML.TO, coll_name + "/" + verb);
613 //logger.info(GSXML.xmlNodeToString(req));
614 Node n = mr.process(msg);
615 Element e = converter.nodeToElement(n);
616 result = collectAll(result, e, verb, OAIXML.RECORD);
617
618 //clear the content of the old request element
619 msg.removeChild(req);
620 req = null;
621 }
622 } else {
623 req.setAttribute(OAIXML.TO, coll_name + "/" + verb);
624
625 Node result_node = mr.process(msg);
626 result = converter.nodeToElement(result_node);
627 }
628
629 if (result == null) {
630 logger.info("message router returns null");
631 return getMessage(OAIXML.createErrorElement("Internal service returns null", ""));
632 }
633 Element res = (Element)GSXML.getChildByTagName(result, OAIXML.RESPONSE);
634 if(res == null) {
635 logger.info("response element in xml_result is null");
636 return getMessage(OAIXML.createErrorElement("Internal service returns null", ""));
637 }
638 NodeList record_list = res.getElementsByTagName(OAIXML.RECORD);
639 int num_records = record_list.getLength();
640 if(num_records == 0) {
641 logger.info("message router returns 0 records.");
642 return getMessage(OAIXML.createErrorElement(OAIXML.NO_RECORDS_MATCH, ""));
643 }
644
645 //The request coming in does not contain a token, but we have to check the resume_after value and see if we need to issue a resumption token and
646 // save the token as well.
647 if (token.equals("") == true) {
648 if(resume_after < 0 || num_records <= resume_after) {
649 //send the whole list of records
650 return result;
651 }
652
653 //append required number of records (may be a complete or incomplete list)
654 getRecords(list_records, record_list, 0, resume_after);
655 //An incomplete list is sent; append a resumptionToken element
656 Element token_elem = createResumptionTokenElement(num_records, 0, resume_after, true);
657 //store this token
658 OAIXML.addToken(token_elem);
659
660 list_records.appendChild(token_elem);
661 return getMessage(list_records);
662 }
663
664 if (token.equals("") == false) {
665 //get an appropriate number of records (partial list) according to the token
666 //take out the cursor value, which is the size of previously sent list
667 int index = token.indexOf(":");
668 int cursor = Integer.parseInt(token.substring(index + 1));
669 Element token_elem = null;
670
671 // are we sending the final part of a complete list?
672 if(cursor + resume_after >= num_records) {
673 //Yes, we are.
674 //append required records to list_records (list is complete)
675 getRecords(list_records, record_list, cursor, num_records);
676 //An incomplete list is sent; append a resumptionToken element
677 token_elem = createResumptionTokenElement(num_records, cursor, -1, false);
678 list_records.appendChild(token_elem);
679 } else {
680 //No, we are not.
681 //append required records to list_records (list is incomplete)
682 getRecords(list_records, record_list, cursor, cursor + resume_after);
683 token_elem = createResumptionTokenElement(num_records, cursor, cursor + resume_after, true);
684 //store this token
685 OAIXML.addToken(token_elem);
686 list_records.appendChild(token_elem);
687 }
688
689 return getMessage(list_records);
690 }//end of if(!token.equals(""))
691
692 return result;//a backup return
693 }
694 // method exclusively used by doListRecords/doListIdentifiers
695 private void getRecords(Element verb_elem, NodeList list, int start_point, int end_point) {
696 for (int i=start_point; i<end_point; i++) {
697 verb_elem.appendChild(verb_elem.getOwnerDocument().importNode(list.item(i), true));
698 }
699 }
700 private Element collectAll(Element result, Element msg, String verb, String elem_name) {
701 if(result == null) {
702 //in the first round, result is null
703 return msg;
704 }
705 Element res_in_result = (Element)GSXML.getChildByTagName(result, OAIXML.RESPONSE);
706 Element verb_elem = (Element)GSXML.getChildByTagName(res_in_result, verb);
707 if(msg == null) {
708 return result;
709 }
710
711 //e.g., get all <record> elements from the returned message. There may be none of
712 //such element, for example, the collection service returned an error message
713 NodeList elem_list = msg.getElementsByTagName(elem_name);
714
715 for (int i=0; i<elem_list.getLength(); i++) {
716 verb_elem.appendChild(res_in_result.getOwnerDocument().importNode(elem_list.item(i), true));
717 }
718 return result;
719 }
720 /** there are three possible exception conditions: bad argument, idDoesNotExist, and noMetadataFormats.
721 * The first one is handled here, and the last two are processed by OAIPMH.
722 */
723 private Element doListMetadataFormats(Element msg) {
724 //if the verb is ListMetadataFormats, there could be only one parameter: identifier
725 //, or there is no parameter; otherwise it is an error
726 //logger.info("" + this.converter.getString(msg));
727
728 Element list_metadata_formats = OAIXML.createElement(OAIXML.LIST_METADATA_FORMATS);
729
730 Element req = (Element)GSXML.getChildByTagName(msg, GSXML.REQUEST_ELEM);
731 if (req == null) { logger.error(""); return null; }
732 NodeList params = GSXML.getChildrenByTagName(req, OAIXML.PARAM);
733 Element param = null;
734 if(params.getLength() == 0) {
735 //this is requesting metadata formats for the whole repository
736 //read the oaiConfig.xml file, return the metadata formats specified there.
737 Element oai_config = OAIXML.getOAIConfigXML();
738 if (oai_config == null) {
739 return getMessage(OAIXML.createErrorElement(OAIXML.ERROR, OAIXML.SERVICE_UNAVAILABLE));
740 } else {
741 Element format_list = (Element)GSXML.getChildByTagName(oai_config, OAIXML.LIST_METADATA_FORMATS);
742 if(format_list == null) {
743 logger.error("OAIConfig.xml must contain the supported metadata formats");
744 return getMessage(list_metadata_formats);
745 }
746 NodeList formats = format_list.getElementsByTagName(OAIXML.METADATA_FORMAT);
747 for(int i=0; i<formats.getLength(); i++) {
748 Element meta_fmt = OAIXML.createElement(OAIXML.METADATA_FORMAT);
749 Element first_meta_format = (Element)formats.item(i);
750 //the element also contains mappings, but we don't want them
751 meta_fmt.appendChild(meta_fmt.getOwnerDocument().importNode(GSXML.getChildByTagName(first_meta_format, OAIXML.METADATA_PREFIX), true));
752 meta_fmt.appendChild(meta_fmt.getOwnerDocument().importNode(GSXML.getChildByTagName(first_meta_format, OAIXML.SCHEMA), true));
753 meta_fmt.appendChild(meta_fmt.getOwnerDocument().importNode(GSXML.getChildByTagName(first_meta_format, OAIXML.METADATA_NAMESPACE), true));
754 list_metadata_formats.appendChild(meta_fmt);
755 }
756 return getMessage(list_metadata_formats);
757 }
758
759 } else if (params.getLength() > 1) {
760 //Bad argument. Can't be more than one parameters for ListMetadataFormats verb
761 return getMessage(OAIXML.createErrorElement(OAIXML.BAD_ARGUMENT, ""));
762 } else {
763 // This is a request for the metadata of a particular item with an identifier
764 /**the request xml is in the form: <request>
765 * <param name=.../>
766 * </request>
767 *And there is a param element and one element only. (No paramList element in between).
768 */
769 param = (Element)params.item(0);
770 String param_name = param.getAttribute(OAIXML.NAME);
771 String identifier = "";
772 if (!param_name.equals(OAIXML.IDENTIFIER)) {
773 //Bad argument
774 return getMessage(OAIXML.createErrorElement(OAIXML.BAD_ARGUMENT, ""));
775 } else {
776 identifier = param.getAttribute(OAIXML.VALUE);
777 // the identifier is in the form: <site_name>:<coll_name>:<OID>
778 // so it must contain at least two ':' characters
779 String[] strs = identifier.split(":");
780 if(strs == null || strs.length < 3) {
781 // the OID may also contain ':'
782 logger.error("identifier is not in the form site:coll:id" + identifier);
783 return getMessage(OAIXML.createErrorElement(OAIXML.ID_DOES_NOT_EXIST, ""));
784 }
785
786 // send request to message router
787 // get the names
788 strs = splitNames(identifier);
789 String name_of_site = strs[0];
790 String coll_name = strs[1];
791 String oid = strs[2];
792
793 //re-organize the request element
794 // reset the 'to' attribute
795 String verb = req.getAttribute(OAIXML.TO);
796 req.setAttribute(OAIXML.TO, coll_name + "/" + verb);
797 // reset the identifier element
798 param.setAttribute(OAIXML.NAME, OAIXML.OID);
799 param.setAttribute(OAIXML.VALUE, oid);
800
801 //Now send the request to the message router to process
802 Node result_node = mr.process(msg);
803 return converter.nodeToElement(result_node);
804 }
805 }
806
807 }
808 private void appendParam(Element req, String name, String value) {
809 Element param = req.getOwnerDocument().createElement(OAIXML.PARAM);
810 param.setAttribute(OAIXML.NAME, name);
811 param.setAttribute(OAIXML.VALUE, value);
812 req.appendChild(param);
813 }
814 private void copyElement(Element identify, String tag_name) {
815 Element from_repository_name = (Element)GSXML.getChildByTagName(oai_config, tag_name);
816 if(from_repository_name != null) {
817 Element this_repository_name = OAIXML.createElement(tag_name);
818 GSXML.setNodeText(this_repository_name, GSXML.getNodeText(from_repository_name));
819 identify.appendChild(this_repository_name);
820 }
821 }
822 private Element doIdentify() {
823 //The validation for this verb has been done in OAIServer.validate(). So no bother here.
824 logger.info("");
825
826 Element identify = OAIXML.createElement(OAIXML.IDENTIFY);
827 //do the repository name
828 copyElement(identify, OAIXML.REPOSITORY_NAME);
829 //do the baseurl
830 copyElement(identify, OAIXML.BASE_URL);
831 //do the protocol version
832 copyElement(identify, OAIXML.PROTOCOL_VERSION);
833 //do the deletedRecord
834 copyElement(identify, OAIXML.DELETED_RECORD);
835 //do the granularity
836 copyElement(identify, OAIXML.GRANULARITY);
837
838 //There can be more than one admin email according to the OAI specification
839 NodeList admin_emails = GSXML.getChildrenByTagName(oai_config, OAIXML.ADMIN_EMAIL);
840 int num_admin = 0;
841 Element from_admin_email = null;
842 if (admin_emails != null) {
843 num_admin = admin_emails.getLength();
844 }
845 for (int i=0; i<num_admin; i++) {
846 copyElement(identify, OAIXML.ADMIN_EMAIL);
847 }
848
849 //do the earliestDatestamp
850 long lastmodified = System.currentTimeMillis();
851 //send request to mr to search through the earliest datestamp amongst all oai collections in the repository.
852 //ask the message router for a list of oai collections
853 NodeList oai_coll = getOAICollectionList();
854 int oai_coll_size = oai_coll.getLength();
855 if (oai_coll_size == 0) {
856 logger.info("returned oai collection list is empty. Set repository earliestDatestamp to be 1970-01-01.");
857 lastmodified = 0;
858 }
859 //the collection build time is determined by the last modified time of the buildConfig.xml file
860 for(int i=0; i<oai_coll_size; i++) {
861 long coll_build_time = Long.parseLong(((Element)oai_coll.item(i)).getAttribute(OAIXML.LASTMODIFIED));
862 lastmodified = (lastmodified > coll_build_time)? coll_build_time : lastmodified;
863 }
864 String earliestDatestamp_str = OAIXML.getTime(lastmodified);
865 Element earliestDatestamp_elem = OAIXML.createElement(OAIXML.EARLIEST_DATESTAMP);
866 GSXML.setNodeText(earliestDatestamp_elem, earliestDatestamp_str);
867 identify.appendChild(earliestDatestamp_elem);
868
869 return getMessage(identify);
870 }
871 //split setSpec (site_name:coll_name) into an array of strings
872 //It has already been checked that the set_spec contains at least one ':'
873 private String[] splitSetSpec(String set_spec) {
874 logger.info(set_spec);
875 String[] strs = new String[2];
876 int colon_index = set_spec.indexOf(":");
877 strs[0] = set_spec.substring(0, colon_index);
878 strs[1] = set_spec.substring(colon_index + 1);
879 return strs;
880 }
881 /** split the identifier into <site + collection + OID> as an array
882 It has already been checked that the 'identifier' contains at least two ':'
883 */
884 private String[] splitNames(String identifier) {
885 logger.info(identifier);
886 String [] strs = new String[3];
887 int first_colon = identifier.indexOf(":");
888 strs[0] = identifier.substring(0, first_colon);
889
890 String sr = identifier.substring(first_colon + 1);
891 int second_colon = sr.indexOf(":");
892 //logger.error(first_colon + " " + second_colon);
893 strs[1] = sr.substring(0, second_colon);
894
895 strs[2] = sr.substring(second_colon + 1);
896 return strs;
897 }
898 /** validate if the specified metadata prefix value is supported by the repository
899 * by checking it in the OAIConfig.xml
900 */
901 private boolean containsMetadataPrefix(String prefix_value) {
902 NodeList prefix_list = oai_config.getElementsByTagName(OAIXML.METADATA_PREFIX);
903
904 for(int i=0; i<prefix_list.getLength(); i++) {
905 if(prefix_value.equals(GSXML.getNodeText((Element)prefix_list.item(i)).trim() )) {
906 return true;
907 }
908 }
909 return false;
910 }
911 private Element doGetRecord(Element msg){
912 logger.info("");
913 /** arguments:
914 identifier: required
915 metadataPrefix: required
916 * Exceptions: badArgument; cannotDisseminateFormat; idDoesNotExist
917 */
918 Element get_record = OAIXML.createElement(OAIXML.GET_RECORD);
919
920 HashSet valid_strs = new HashSet();
921 valid_strs.add(OAIXML.IDENTIFIER);
922 valid_strs.add(OAIXML.METADATA_PREFIX);
923
924 Element req = (Element)GSXML.getChildByTagName(msg, GSXML.REQUEST_ELEM);
925 NodeList params = GSXML.getChildrenByTagName(req, OAIXML.PARAM);
926 HashMap param_map = OAIXML.getParamMap(params);
927
928 if(!isValidParam(param_map, valid_strs) ||
929 params.getLength() == 0 ||
930 param_map.containsKey(OAIXML.IDENTIFIER) == false ||
931 param_map.containsKey(OAIXML.METADATA_PREFIX) == false ) {
932 logger.error("must have the metadataPrefix/identifier parameter.");
933 return getMessage(OAIXML.createErrorElement(OAIXML.BAD_ARGUMENT, ""));
934 }
935
936 String prefix = (String)param_map.get(OAIXML.METADATA_PREFIX);
937 String identifier = (String)param_map.get(OAIXML.IDENTIFIER);
938
939 // verify the metadata prefix
940 if (containsMetadataPrefix(prefix) == false) {
941 logger.error("requested prefix is not found in OAIConfig.xml");
942 return getMessage(OAIXML.createErrorElement(OAIXML.CANNOT_DISSEMINATE_FORMAT, ""));
943 }
944
945 // get the names
946 String[] strs = splitNames(identifier);
947 if(strs == null || strs.length < 3) {
948 logger.error("identifier is not in the form site:coll:id" + identifier);
949 return getMessage(OAIXML.createErrorElement(OAIXML.ID_DOES_NOT_EXIST, ""));
950 }
951 String name_of_site = strs[0];
952 String coll_name = strs[1];
953 String oid = strs[2];
954
955 //re-organize the request element
956 // reset the 'to' attribute
957 String verb = req.getAttribute(OAIXML.TO);
958 req.setAttribute(OAIXML.TO, coll_name + "/" + verb);
959 // reset the identifier element
960 Element param = GSXML.getNamedElement(req, OAIXML.PARAM, OAIXML.NAME, OAIXML.IDENTIFIER);
961 if (param != null) {
962 param.setAttribute(OAIXML.NAME, OAIXML.OID);
963 param.setAttribute(OAIXML.VALUE, oid);
964 }
965
966 //Now send the request to the message router to process
967 Node result_node = mr.process(msg);
968 return converter.nodeToElement(result_node);
969 }
970}
Note: See TracBrowser for help on using the repository browser.