source: greenstone3/branches/customizingGreenstone3/src/java/org/greenstone/gsdl3/core/OAIReceptionist.java@ 14713

Last change on this file since 14713 was 14211, checked in by xiao, 17 years ago

an exclusive receptionist handling OAI requests

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