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

Last change on this file since 23901 was 23901, checked in by ak19, 13 years ago

Getting GS3's OAIserver to work again. New GS3 collections are OAI-enabled by default, since they automatically get the OAIPMH ServiceRack element added into their collectionConfig.xml (from gs2build's modelcol of collectionConfig.xml). Small adjustments to the code ensure that collections that are not OAI-enabled do not appear in the list of cross-collection Identifiers and Records, and that whenever such collections are present, the Identifiers and Records can still be collected for the OAI-enabled collections without the ListIdentifiers and ListRecords requests failing.

File size: 41.0 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 if(res_in_result == null) { // return the results of all other collections accumulated so far
707 return msg;
708 }
709 Element verb_elem = (Element)GSXML.getChildByTagName(res_in_result, verb);
710 if(msg == null) {
711 return result;
712 }
713
714 //e.g., get all <record> elements from the returned message. There may be none of
715 //such element, for example, the collection service returned an error message
716 NodeList elem_list = msg.getElementsByTagName(elem_name);
717
718 for (int i=0; i<elem_list.getLength(); i++) {
719 verb_elem.appendChild(res_in_result.getOwnerDocument().importNode(elem_list.item(i), true));
720 }
721 return result;
722 }
723 /** there are three possible exception conditions: bad argument, idDoesNotExist, and noMetadataFormats.
724 * The first one is handled here, and the last two are processed by OAIPMH.
725 */
726 private Element doListMetadataFormats(Element msg) {
727 //if the verb is ListMetadataFormats, there could be only one parameter: identifier
728 //, or there is no parameter; otherwise it is an error
729 //logger.info("" + this.converter.getString(msg));
730
731 Element list_metadata_formats = OAIXML.createElement(OAIXML.LIST_METADATA_FORMATS);
732
733 Element req = (Element)GSXML.getChildByTagName(msg, GSXML.REQUEST_ELEM);
734 if (req == null) { logger.error(""); return null; }
735 NodeList params = GSXML.getChildrenByTagName(req, OAIXML.PARAM);
736 Element param = null;
737 if(params.getLength() == 0) {
738 //this is requesting metadata formats for the whole repository
739 //read the oaiConfig.xml file, return the metadata formats specified there.
740 Element oai_config = OAIXML.getOAIConfigXML();
741 if (oai_config == null) {
742 return getMessage(OAIXML.createErrorElement(OAIXML.ERROR, OAIXML.SERVICE_UNAVAILABLE));
743 } else {
744 Element format_list = (Element)GSXML.getChildByTagName(oai_config, OAIXML.LIST_METADATA_FORMATS);
745 if(format_list == null) {
746 logger.error("OAIConfig.xml must contain the supported metadata formats");
747 return getMessage(list_metadata_formats);
748 }
749 NodeList formats = format_list.getElementsByTagName(OAIXML.METADATA_FORMAT);
750 for(int i=0; i<formats.getLength(); i++) {
751 Element meta_fmt = OAIXML.createElement(OAIXML.METADATA_FORMAT);
752 Element first_meta_format = (Element)formats.item(i);
753 //the element also contains mappings, but we don't want them
754 meta_fmt.appendChild(meta_fmt.getOwnerDocument().importNode(GSXML.getChildByTagName(first_meta_format, OAIXML.METADATA_PREFIX), true));
755 meta_fmt.appendChild(meta_fmt.getOwnerDocument().importNode(GSXML.getChildByTagName(first_meta_format, OAIXML.SCHEMA), true));
756 meta_fmt.appendChild(meta_fmt.getOwnerDocument().importNode(GSXML.getChildByTagName(first_meta_format, OAIXML.METADATA_NAMESPACE), true));
757 list_metadata_formats.appendChild(meta_fmt);
758 }
759 return getMessage(list_metadata_formats);
760 }
761
762 } else if (params.getLength() > 1) {
763 //Bad argument. Can't be more than one parameters for ListMetadataFormats verb
764 return getMessage(OAIXML.createErrorElement(OAIXML.BAD_ARGUMENT, ""));
765 } else {
766 // This is a request for the metadata of a particular item with an identifier
767 /**the request xml is in the form: <request>
768 * <param name=.../>
769 * </request>
770 *And there is a param element and one element only. (No paramList element in between).
771 */
772 param = (Element)params.item(0);
773 String param_name = param.getAttribute(OAIXML.NAME);
774 String identifier = "";
775 if (!param_name.equals(OAIXML.IDENTIFIER)) {
776 //Bad argument
777 return getMessage(OAIXML.createErrorElement(OAIXML.BAD_ARGUMENT, ""));
778 } else {
779 identifier = param.getAttribute(OAIXML.VALUE);
780 // the identifier is in the form: <site_name>:<coll_name>:<OID>
781 // so it must contain at least two ':' characters
782 String[] strs = identifier.split(":");
783 if(strs == null || strs.length < 3) {
784 // the OID may also contain ':'
785 logger.error("identifier is not in the form site:coll:id" + identifier);
786 return getMessage(OAIXML.createErrorElement(OAIXML.ID_DOES_NOT_EXIST, ""));
787 }
788
789 // send request to message router
790 // get the names
791 strs = splitNames(identifier);
792 String name_of_site = strs[0];
793 String coll_name = strs[1];
794 String oid = strs[2];
795
796 //re-organize the request element
797 // reset the 'to' attribute
798 String verb = req.getAttribute(OAIXML.TO);
799 req.setAttribute(OAIXML.TO, coll_name + "/" + verb);
800 // reset the identifier element
801 param.setAttribute(OAIXML.NAME, OAIXML.OID);
802 param.setAttribute(OAIXML.VALUE, oid);
803
804 //Now send the request to the message router to process
805 Node result_node = mr.process(msg);
806 return converter.nodeToElement(result_node);
807 }
808 }
809
810 }
811 private void appendParam(Element req, String name, String value) {
812 Element param = req.getOwnerDocument().createElement(OAIXML.PARAM);
813 param.setAttribute(OAIXML.NAME, name);
814 param.setAttribute(OAIXML.VALUE, value);
815 req.appendChild(param);
816 }
817 private void copyElement(Element identify, String tag_name) {
818 Element from_repository_name = (Element)GSXML.getChildByTagName(oai_config, tag_name);
819 if(from_repository_name != null) {
820 Element this_repository_name = OAIXML.createElement(tag_name);
821 GSXML.setNodeText(this_repository_name, GSXML.getNodeText(from_repository_name));
822 identify.appendChild(this_repository_name);
823 }
824 }
825 private Element doIdentify() {
826 //The validation for this verb has been done in OAIServer.validate(). So no bother here.
827 logger.info("");
828
829 Element identify = OAIXML.createElement(OAIXML.IDENTIFY);
830 //do the repository name
831 copyElement(identify, OAIXML.REPOSITORY_NAME);
832 //do the baseurl
833 copyElement(identify, OAIXML.BASE_URL);
834 //do the protocol version
835 copyElement(identify, OAIXML.PROTOCOL_VERSION);
836 //do the deletedRecord
837 copyElement(identify, OAIXML.DELETED_RECORD);
838 //do the granularity
839 copyElement(identify, OAIXML.GRANULARITY);
840
841 //There can be more than one admin email according to the OAI specification
842 NodeList admin_emails = GSXML.getChildrenByTagName(oai_config, OAIXML.ADMIN_EMAIL);
843 int num_admin = 0;
844 Element from_admin_email = null;
845 if (admin_emails != null) {
846 num_admin = admin_emails.getLength();
847 }
848 for (int i=0; i<num_admin; i++) {
849 copyElement(identify, OAIXML.ADMIN_EMAIL);
850 }
851
852 //do the earliestDatestamp
853 long lastmodified = System.currentTimeMillis();
854 //send request to mr to search through the earliest datestamp amongst all oai collections in the repository.
855 //ask the message router for a list of oai collections
856 NodeList oai_coll = getOAICollectionList();
857 int oai_coll_size = oai_coll.getLength();
858 if (oai_coll_size == 0) {
859 logger.info("returned oai collection list is empty. Set repository earliestDatestamp to be 1970-01-01.");
860 lastmodified = 0;
861 }
862 //the collection build time is determined by the last modified time of the buildConfig.xml file
863 for(int i=0; i<oai_coll_size; i++) {
864 long coll_build_time = Long.parseLong(((Element)oai_coll.item(i)).getAttribute(OAIXML.LASTMODIFIED));
865 lastmodified = (lastmodified > coll_build_time)? coll_build_time : lastmodified;
866 }
867 String earliestDatestamp_str = OAIXML.getTime(lastmodified);
868 Element earliestDatestamp_elem = OAIXML.createElement(OAIXML.EARLIEST_DATESTAMP);
869 GSXML.setNodeText(earliestDatestamp_elem, earliestDatestamp_str);
870 identify.appendChild(earliestDatestamp_elem);
871
872 return getMessage(identify);
873 }
874 //split setSpec (site_name:coll_name) into an array of strings
875 //It has already been checked that the set_spec contains at least one ':'
876 private String[] splitSetSpec(String set_spec) {
877 logger.info(set_spec);
878 String[] strs = new String[2];
879 int colon_index = set_spec.indexOf(":");
880 strs[0] = set_spec.substring(0, colon_index);
881 strs[1] = set_spec.substring(colon_index + 1);
882 return strs;
883 }
884 /** split the identifier into <site + collection + OID> as an array
885 It has already been checked that the 'identifier' contains at least two ':'
886 */
887 private String[] splitNames(String identifier) {
888 logger.info(identifier);
889 String [] strs = new String[3];
890 int first_colon = identifier.indexOf(":");
891 strs[0] = identifier.substring(0, first_colon);
892
893 String sr = identifier.substring(first_colon + 1);
894 int second_colon = sr.indexOf(":");
895 //logger.error(first_colon + " " + second_colon);
896 strs[1] = sr.substring(0, second_colon);
897
898 strs[2] = sr.substring(second_colon + 1);
899 return strs;
900 }
901 /** validate if the specified metadata prefix value is supported by the repository
902 * by checking it in the OAIConfig.xml
903 */
904 private boolean containsMetadataPrefix(String prefix_value) {
905 NodeList prefix_list = oai_config.getElementsByTagName(OAIXML.METADATA_PREFIX);
906
907 for(int i=0; i<prefix_list.getLength(); i++) {
908 if(prefix_value.equals(GSXML.getNodeText((Element)prefix_list.item(i)).trim() )) {
909 return true;
910 }
911 }
912 return false;
913 }
914 private Element doGetRecord(Element msg){
915 logger.info("");
916 /** arguments:
917 identifier: required
918 metadataPrefix: required
919 * Exceptions: badArgument; cannotDisseminateFormat; idDoesNotExist
920 */
921 Element get_record = OAIXML.createElement(OAIXML.GET_RECORD);
922
923 HashSet valid_strs = new HashSet();
924 valid_strs.add(OAIXML.IDENTIFIER);
925 valid_strs.add(OAIXML.METADATA_PREFIX);
926
927 Element req = (Element)GSXML.getChildByTagName(msg, GSXML.REQUEST_ELEM);
928 NodeList params = GSXML.getChildrenByTagName(req, OAIXML.PARAM);
929 HashMap param_map = OAIXML.getParamMap(params);
930
931 if(!isValidParam(param_map, valid_strs) ||
932 params.getLength() == 0 ||
933 param_map.containsKey(OAIXML.IDENTIFIER) == false ||
934 param_map.containsKey(OAIXML.METADATA_PREFIX) == false ) {
935 logger.error("must have the metadataPrefix/identifier parameter.");
936 return getMessage(OAIXML.createErrorElement(OAIXML.BAD_ARGUMENT, ""));
937 }
938
939 String prefix = (String)param_map.get(OAIXML.METADATA_PREFIX);
940 String identifier = (String)param_map.get(OAIXML.IDENTIFIER);
941
942 // verify the metadata prefix
943 if (containsMetadataPrefix(prefix) == false) {
944 logger.error("requested prefix is not found in OAIConfig.xml");
945 return getMessage(OAIXML.createErrorElement(OAIXML.CANNOT_DISSEMINATE_FORMAT, ""));
946 }
947
948 // get the names
949 String[] strs = splitNames(identifier);
950 if(strs == null || strs.length < 3) {
951 logger.error("identifier is not in the form site:coll:id" + identifier);
952 return getMessage(OAIXML.createErrorElement(OAIXML.ID_DOES_NOT_EXIST, ""));
953 }
954 String name_of_site = strs[0];
955 String coll_name = strs[1];
956 String oid = strs[2];
957
958 //re-organize the request element
959 // reset the 'to' attribute
960 String verb = req.getAttribute(OAIXML.TO);
961 req.setAttribute(OAIXML.TO, coll_name + "/" + verb);
962 // reset the identifier element
963 Element param = GSXML.getNamedElement(req, OAIXML.PARAM, OAIXML.NAME, OAIXML.IDENTIFIER);
964 if (param != null) {
965 param.setAttribute(OAIXML.NAME, OAIXML.OID);
966 param.setAttribute(OAIXML.VALUE, oid);
967 }
968
969 //Now send the request to the message router to process
970 Node result_node = mr.process(msg);
971 return converter.nodeToElement(result_node);
972 }
973}
Note: See TracBrowser for help on using the repository browser.