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

Last change on this file since 32868 was 32868, checked in by ak19, 5 years ago

After 3 attempts, finally succeeded in implementing Kathy's suggestion: between deactivating and activating an OAICollection (during the period when building gets moved to index), the OAI collection should indeed still be listed among the Sets (collections) served by the OAICollection, so that DL users know that the collection exists and will continue to exist. But the failure of getting the Identifiers and Records for a deactivated OAI collection must instead produce a message telling users they're only temporarily unavailable and to check back later.

File size: 51.3 KB
Line 
1/*
2 * OAIReceptionist.java
3 * Copyright (C) 2012 New Zealand Digital Library, http://www.nzdl.org
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18 */
19
20package org.greenstone.gsdl3.core;
21
22import org.greenstone.gsdl3.util.*;
23import org.greenstone.gsdl3.action.*;
24// XML classes
25import org.w3c.dom.Node;
26import org.w3c.dom.NodeList;
27import org.w3c.dom.Document;
28import org.w3c.dom.Element;
29
30// other java classes
31import java.io.File;
32import java.util.*;
33
34import org.apache.log4j.*;
35
36/** a Receptionist, used for oai metadata response xml generation.
37 * This receptionist talks to the message router directly,
38 * instead of via any action, hence no action map is needed.
39 * @see the basic Receptionist
40 */
41public class OAIReceptionist implements ModuleInterface {
42
43 static Logger logger = Logger.getLogger(org.greenstone.gsdl3.core.OAIReceptionist.class.getName());
44
45 /** Instead of a config_params object, only a site_name is needed by oai receptionist. */
46 protected String site_name = null;
47 /** The unique repository identifier */
48 protected String repository_id = null;
49
50 /** the configure file of this receptionist passed from the oai servlet. */
51 protected Element oai_config = null;
52
53 /** contained in the OAIConfig.xml deciding whether the resumptionToken should be in use */
54 protected int resume_after = -1 ;
55
56 /** the message router that the Receptionist and Actions will talk to */
57 protected ModuleInterface mr = null;
58
59 // Some of the data/responses will not change while the servlet is running, so
60 // we can cache them
61
62 /** A list of all the collections available to this OAI server */
63 protected Element collection_list = null;
64 /** a vector of the names, for convenience */
65 protected Vector<String> collection_name_list = null;
66 /** a vector to be maintained in parallel to Vector collection_name_list in terms of adds/removes.
67 * This is a list of Integers that denote whether the associated collection in collection_name_list is currently ACTIVE or DEACTIVATED.
68 * The state for any collection can be changed when the building folder is moved to index during rebuilding, before and after which
69 * activateOrDeactivateCollection() here is called to deactivate a collection and then activate it again, respectively.
70 * Can't make collection_name_list a HashMap, since a map's keys are unique (i.e. a Set) whereas collection_name_list is a Vector.
71 * I've confirmed that collection_name_list is specifically a list of OAI collection names, not non-OAI collections.
72 */
73 protected Vector<Integer> collection_state_list = null;
74 /** Possible states for a collection */
75 protected static final Integer DEACTIVATED = new Integer(0);
76 protected static final Integer ACTIVE = new Integer(1);
77
78 /** If this is true, then there are no OAI enabled collections, so can always return noRecordsMatch (after validating the request params) */
79 protected boolean noRecordsMatch = false;
80
81 /** A set of all known 'sets' */
82 protected HashSet<String> set_set = null;
83
84 protected boolean has_super_colls = false;
85 /** a hash of super set-> collection list */
86 protected HashMap<String, Vector<String>> super_coll_map = null;
87 /** store the super coll elements for convenience */
88 HashMap<String, Element> super_coll_data = null;
89 /** store the metadata formats ??????*/
90 /** The identify response */
91 protected Element identify_response = null;
92 /** The list set response */
93 protected Element listsets_response = null;
94 /** the list metadata formats response */
95 protected Element listmetadataformats_response = null;
96
97 public OAIReceptionist() {
98
99 }
100
101 public void cleanUp() {
102 if (this.mr != null) {
103
104 this.mr.cleanUp();
105 }
106 OAIResumptionToken.saveTokensToFile();
107 }
108
109 public void setSiteName(String site_name) {
110 this.site_name = site_name;
111 }
112 /** sets the message router - it should already be created and
113 * configured in the init() of a servlet (OAIServer, for example) before being passed to the receptionist*/
114 public void setMessageRouter(ModuleInterface mr) {
115 this.mr = mr;
116 }
117
118 /** configures the receptionist */
119 public boolean configure(Element config) {
120
121 if (this.mr==null) {
122 logger.error(" message routers must be set before calling oai configure");
123 return false;
124 }
125 if (config == null) {
126 logger.error(" oai configure file is null");
127 return false;
128 }
129 oai_config = config;
130 resume_after = getResumeAfter();
131
132 repository_id = getRepositoryIdentifier();
133 configureSuperSetInfo();
134 if (!configureSetInfo()) {
135 // there are no sets
136 logger.error("No sets (collections) available for OAI");
137 return false;
138 }
139
140 // load in tokens from OAIResumptionToken.xml, and then clear out any
141 // expired ones.
142 OAIResumptionToken.init();
143 OAIResumptionToken.clearExpiredTokens();
144
145 return true;
146 }
147
148 // assuming that sets are static. If collections change then the servlet
149 // should be restarted.
150 private boolean configureSuperSetInfo() {
151 // do we have any super colls listed in web/WEB-INF/classes/OAIConfig.xml?
152 // Will be like
153 // <oaiSuperSet>
154 // <SetSpec>xxx</SetSpec>
155 // <setName>xxx</SetName>
156 // <SetDescription>xxx</setDescription>
157 // </oaiSuperSet>
158 // The super set is listed in OAIConfig, and collections themselves state
159 // whether they are part of the super set or not.
160 NodeList super_coll_list = this.oai_config.getElementsByTagName(OAIXML.OAI_SUPER_SET);
161 this.super_coll_data = new HashMap<String, Element>();
162 if (super_coll_list.getLength() > 0) {
163 this.has_super_colls = true;
164 for (int i=0; i<super_coll_list.getLength(); i++) {
165 Element super_coll = (Element)super_coll_list.item(i);
166 Element set_spec = (Element)GSXML.getChildByTagName(super_coll, OAIXML.SET_SPEC);
167 if (set_spec != null) {
168 String name = GSXML.getNodeText(set_spec);
169 if (!name.equals("")) {
170 this.super_coll_data.put(name, super_coll);
171 logger.info("adding in super coll "+name);
172 }
173 }
174 }
175
176 if (this.super_coll_data.size()==0) {
177 this.has_super_colls = false;
178 }
179 }
180 if (this.has_super_colls == true) {
181 this.super_coll_map = new HashMap<String, Vector<String>>();
182 }
183 return true;
184
185 }
186 private boolean configureSetInfo() {
187 this.set_set = new HashSet<String>();
188
189 // First, we get a list of all the OAI enabled collections
190 // We get this by sending a listSets request to the MR
191 Document doc = XMLConverter.newDOM();
192 Element message = doc.createElement(GSXML.MESSAGE_ELEM);
193
194 Element request = GSXML.createBasicRequest(doc, OAIXML.OAI_SET_LIST, "", null);
195 message.appendChild(request);
196 Node msg_node = mr.process(message);
197
198 if (msg_node == null) {
199 logger.error("returned msg_node from mr is null");
200 return false;
201 }
202 Element resp = (Element)GSXML.getChildByTagName(msg_node, GSXML.RESPONSE_ELEM);
203 Element coll_list = (Element)GSXML.getChildByTagName(resp, GSXML.COLLECTION_ELEM + GSXML.LIST_MODIFIER);
204 if (coll_list == null) {
205 logger.error("coll_list is null");
206 return false;
207 }
208
209 this.collection_list = (Element)doc.importNode(coll_list, true);
210
211 // go through and store a list of collection names for convenience
212 // also create a 'to' attribute for the next request to the MR, which
213 // is a ListSets request to each collection
214 Node child = this.collection_list.getFirstChild();
215 if (child == null) {
216 logger.error("collection list has no children");
217 noRecordsMatch = true;
218 return false;
219 }
220
221 this.collection_name_list = new Vector<String>();
222 this.collection_state_list = new Vector<Integer>();
223 StringBuffer to = new StringBuffer();
224 boolean first = true;
225 while (child != null) {
226 if (child.getNodeName().equals(GSXML.COLLECTION_ELEM)) {
227 String coll_id =((Element) child).getAttribute(GSXML.NAME_ATT);
228 this.collection_name_list.add(coll_id);
229 this.collection_state_list.add(ACTIVE); // collections start out active (they get activated during configuring) until deactivation takes place
230 if (!first) {
231 to.append(',');
232 }
233 first = false;
234 to.append(coll_id+"/"+OAIXML.LIST_SETS);
235 }
236 child = child.getNextSibling();
237 }
238 if (first) {
239 // we haven't found any collections
240 logger.error("found no collection elements in collectionList");
241 noRecordsMatch = true;
242 return false;
243 }
244 Document listsets_doc = XMLConverter.newDOM();
245 Element listsets_element = listsets_doc.createElement(OAIXML.LIST_SETS);
246 this.listsets_response = getMessage(listsets_doc, listsets_element);
247
248 // Now, for each collection, get a list of all its sets
249 // might include subsets (classifiers) or super colls
250 // We'll reuse the first message, changing its type and to atts
251 request.setAttribute(GSXML.TYPE_ATT, "");
252 request.setAttribute(GSXML.TO_ATT, to.toString());
253 // send to MR
254 msg_node = mr.process(message);
255 //logger.info("*** " + XMLConverter.getPrettyString(msg_node));
256 NodeList response_list = ((Element)msg_node).getElementsByTagName(GSXML.RESPONSE_ELEM);
257 for (int c=0; c<response_list.getLength(); c++) {
258 // for each collection's response
259 Element response = (Element)response_list.item(c);
260 String coll_name = GSPath.getFirstLink(response.getAttribute(GSXML.FROM_ATT));
261 logger.info("*** coll from response "+coll_name);
262 NodeList set_list = response.getElementsByTagName(OAIXML.SET);
263 for (int j=0; j<set_list.getLength(); j++) {
264 // now check if it a super collection
265 Element set = (Element)set_list.item(j);
266 String set_spec = GSXML.getNodeText((Element)GSXML.getChildByTagName(set, OAIXML.SET_SPEC));
267 logger.info("*** set spec = "+set_spec);
268 // this may change if we add site name back in
269 // setSpecs will be collname or collname:subset or supercollname
270 if (set_spec.indexOf(":")==-1 && ! set_spec.equals(coll_name)) {
271 // it must be a super coll spec
272 logger.info("*** found super coll, "+set_spec);
273 // check that it is a valid one from config
274 if (this.has_super_colls == true && this.super_coll_data.containsKey(set_spec)) {
275 Vector <String> subcolls = this.super_coll_map.get(set_spec);
276 if (subcolls == null) {
277 logger.info("*** its new!!");
278 // not in there yet
279 subcolls = new Vector<String>();
280 this.set_set.add(set_spec);
281 this.super_coll_map.put(set_spec, subcolls);
282 // the first time a supercoll is mentioned, add into the set list
283 logger.info("*** finding the set info "+XMLConverter.getPrettyString(this.super_coll_data.get(set_spec)));
284 listsets_element.appendChild(GSXML.duplicateWithNewName(listsets_doc, this.super_coll_data.get(set_spec), OAIXML.SET, true));
285 }
286 // add this collection to the list for the super coll
287 subcolls.add(coll_name);
288 }
289 } else { // its either the coll itself or a subcoll
290 // add in the set
291 listsets_element.appendChild(listsets_doc.importNode(set, true));
292 this.set_set.add(set_spec);
293 }
294 } // for each set in the collection
295 } // for each OAI enabled collection
296
297
298 debugListActivatedCollections("List of collections after configuring Set Info");
299 return true;
300 }
301
302 protected void resetMessageRouter() {
303 // we just need to send a configure request to MR
304 Document doc = XMLConverter.newDOM();
305 Element mr_request_message = doc.createElement(GSXML.MESSAGE_ELEM);
306 Element mr_request = GSXML.createBasicRequest(doc, GSXML.REQUEST_TYPE_SYSTEM, "", null);
307 mr_request_message.appendChild(mr_request);
308
309 Element system = doc.createElement(GSXML.SYSTEM_ELEM);
310 mr_request.appendChild(system);
311 system.setAttribute(GSXML.TYPE_ATT, GSXML.SYSTEM_TYPE_CONFIGURE);
312
313 Element response = (Element) this.mr.process(mr_request_message);
314 logger.info("*** configure response = "+XMLConverter.getPrettyString(response));
315 }
316
317 protected boolean activateOrDeactivateCollection(String collName, int activationState) {
318 // Send a request like: a=s&sa=<a|d>&st=collection&sn=<collName>
319 Document doc = XMLConverter.newDOM();
320 Element mr_request_message = doc.createElement(GSXML.MESSAGE_ELEM);
321 Element mr_request = GSXML.createBasicRequest(doc, GSXML.REQUEST_TYPE_SYSTEM, "", null);
322 mr_request_message.appendChild(mr_request);
323
324 Element system = doc.createElement(GSXML.SYSTEM_ELEM);
325 mr_request.appendChild(system);
326 if(activationState == OAIXML.ACTIVATION) {
327 system.setAttribute(GSXML.TYPE_ATT, GSXML.SYSTEM_TYPE_ACTIVATE);
328 } else {
329 system.setAttribute(GSXML.TYPE_ATT, GSXML.SYSTEM_TYPE_DEACTIVATE);
330 }
331 system.setAttribute(GSXML.SYSTEM_MODULE_TYPE_ATT, GSXML.COLLECTION_ELEM);
332 system.setAttribute(GSXML.SYSTEM_MODULE_NAME_ATT, collName);
333
334 Element response = (Element) this.mr.process(mr_request_message);
335 logger.info("*** (de)activate response = "+XMLConverter.getPrettyString(response));
336
337 boolean success = false;
338 NodeList elements = response.getElementsByTagName(GSXML.STATUS_ELEM);
339 if(elements.getLength() <= 0) {
340 logger.error("***** No result status");
341 return false;
342 }
343
344 String result = GSXML.getNodeText((Element)elements.item(0));
345 if(result.contains("could not be")) { // could not be (de)activated
346 return false;
347 } else {
348 // the collection's state has successfully been set to the requested activationState (be it ACTIVE or DEACTIVATED)
349 Integer changedState = (activationState == OAIXML.ACTIVATION) ? ACTIVE : DEACTIVATED;
350 int index = this.collection_name_list.indexOf(collName); // TODO: Check that whatever collname passed in by servercontrol.pm is fully qualified, to account for super/sub collections.
351 if(index != -1) { // shouldn't be? since startup never calls this method. This method is only called when building an existing collection?
352 this.collection_state_list.set(index, changedState);
353 } else {
354 logger.error("@@@@ index == -1, could not find collection " + collName + " in collection_name_list");
355 }
356 debugListActivatedCollections("After activation/deactivation: " + changedState);
357
358 return true;
359 }
360 }
361
362 private void debugListActivatedCollections(String heading) {
363
364 logger.info("#### " + heading + ", colls are: ");
365 for(int i = 0; i < collection_name_list.size(); i++) {
366 logger.info("###### collname: " + collection_name_list.get(i));
367 logger.info("###### state: " + collection_state_list.get(i));
368 }
369 }
370
371 private void debugPrintCurrentCollList(Vector<String> current_coll_list, String heading) {
372 logger.info("@@@@ " + heading + ", colls are: ");
373 for(int i = 0; i < current_coll_list.size(); i++) {
374 String coll = current_coll_list.get(i);
375 logger.info("@@@@@@ collname: " + coll);
376 }
377 }
378
379 /** process using strings - just calls process using Elements */
380 public String process(String xml_in) {
381
382 Node message_node = XMLConverter.getDOM(xml_in);
383 Node page = process(message_node);
384 return XMLConverter.getString(page);
385 }
386
387 //Compose a message/response element used to send back to the OAIServer servlet.
388 //This method is only used within OAIReceptionist
389 private Element getMessage(Document doc, Element e) {
390 Element msg = doc.createElement(GSXML.MESSAGE_ELEM);
391 Element response = doc.createElement(GSXML.RESPONSE_ELEM);
392 msg.appendChild(response);
393 response.appendChild(e);
394 return msg;
395 }
396
397 /** process - produce xml data in response to a request
398 * if something goes wrong, it returns null -
399 */
400 public Node process(Node message_node) {
401 logger.info("*** OAIReceptionist received request");
402
403 Element message = GSXML.nodeToElement(message_node);
404 logger.info("*** " + XMLConverter.getString(message));
405
406 // check that its a correct message tag
407 if (!message.getTagName().equals(GSXML.MESSAGE_ELEM)) {
408 logger.error(" Invalid message. GSDL message should start with <"+GSXML.MESSAGE_ELEM+">, instead it starts with:"+message.getTagName()+".");
409 return OAIXML.createErrorMessage(OAIXML.BAD_ARGUMENT, "Internal messaging error");
410 }
411
412 // get the request out of the message - assume that there is only one
413 Element request = (Element)GSXML.getChildByTagName(message, GSXML.REQUEST_ELEM);
414 if (request == null) {
415 logger.error(" message had no request!");
416 return OAIXML.createErrorMessage(OAIXML.BAD_ARGUMENT, "Internal messaging error");
417 }
418
419 // Special cases: certain non-OAI commands/non-verbs are recognised
420 // special case, reset=true for reloading the MR and recept data
421 String reset = request.getAttribute("reset");
422 if (!reset.equals("")) {
423 resetMessageRouter();
424 configureSetInfo();
425 return OAIXML.createResetResponse(true);
426 }
427
428 // special case 2: activate=<collname> or deactivate=<collname> can be passed to oaiserver servlet too
429 if (request.hasAttribute(GSXML.SYSTEM_TYPE_ACTIVATE)) {
430 String collname = request.getAttribute(GSXML.SYSTEM_TYPE_ACTIVATE);
431 // don't bother activating if it's not an OAI collection
432 if (!this.set_set.contains(collname)) {
433 return OAIXML.createDeActivationOfNonOAICollResponse(OAIXML.ACTIVATION, collname);
434 }
435 boolean success = activateOrDeactivateCollection(collname, OAIXML.ACTIVATION);
436 return OAIXML.createActivationStateResponse(success, OAIXML.ACTIVATION, collname);
437 } else if (request.hasAttribute(GSXML.SYSTEM_TYPE_DEACTIVATE)) {
438 String collname = request.getAttribute(GSXML.SYSTEM_TYPE_DEACTIVATE);
439 // don't bother deactivating if it's not an OAI collection
440 if (!this.set_set.contains(collname)) {
441 return OAIXML.createDeActivationOfNonOAICollResponse(OAIXML.DEACTIVATION, collname);
442 }
443 boolean success = activateOrDeactivateCollection(collname, OAIXML.DEACTIVATION);
444 return OAIXML.createActivationStateResponse(success, OAIXML.DEACTIVATION, collname);
445 }
446
447 //At this stage, the value of 'to' attribute of the request must be the 'verb'
448 //The only thing that the oai receptionist can be sure is that these verbs are valid, nothing else.
449 String verb = request.getAttribute(GSXML.TO_ATT);
450 if (verb.equals(OAIXML.IDENTIFY)) {
451 return doIdentify();
452 }
453 if (verb.equals(OAIXML.LIST_METADATA_FORMATS)) {
454 return doListMetadataFormats(request);
455 }
456 if (verb.equals(OAIXML.LIST_SETS)) {
457 // we have composed the list sets response on init
458 // Note this means that list sets never uses resumption tokens
459 return this.listsets_response;
460 }
461 if (verb.equals(OAIXML.GET_RECORD)) {
462 return doGetRecord(request);
463 }
464 if (verb.equals(OAIXML.LIST_IDENTIFIERS)) {
465 return doListIdentifiersOrRecords(request,OAIXML.LIST_IDENTIFIERS , OAIXML.HEADER);
466 }
467 if (verb.equals(OAIXML.LIST_RECORDS)) {
468 return doListIdentifiersOrRecords(request, OAIXML.LIST_RECORDS, OAIXML.RECORD);
469 }
470 // should never get here as verbs were checked in OAIServer
471 return OAIXML.createErrorMessage(OAIXML.BAD_VERB, "Unexpected things happened");
472
473 }
474
475
476 private int getResumeAfter() {
477 Element resume_after = (Element)GSXML.getChildByTagName(oai_config, OAIXML.RESUME_AFTER);
478 if(resume_after != null) return Integer.parseInt(GSXML.getNodeText(resume_after));
479 return -1;
480 }
481 private String getRepositoryIdentifier() {
482 Element ri = (Element)GSXML.getChildByTagName(oai_config, OAIXML.REPOSITORY_IDENTIFIER);
483 if (ri != null) {
484 return GSXML.getNodeText(ri);
485 }
486 return "";
487 }
488
489
490 /** if the param_map contains strings other than those in valid_strs, return false;
491 * otherwise true.
492 */
493 private boolean areAllParamsValid(HashMap<String, String> param_map, HashSet<String> valid_strs) {
494 ArrayList<String> param_list = new ArrayList<String>(param_map.keySet());
495 for(int i=0; i<param_list.size(); i++) {
496 logger.info("*** param, key = "+param_list.get(i)+", value = "+param_map.get(param_list.get(i)));
497 if (valid_strs.contains(param_list.get(i)) == false) {
498 return false;
499 }
500 }
501 return true;
502 }
503
504 private Element doListIdentifiersOrRecords(Element req, String verb, String record_type) {
505 // options: from, until, set, metadataPrefix, resumptionToken
506 // exceptions: badArgument, badResumptionToken, cannotDisseminateFormat, noRecordMatch, and noSetHierarchy
507 HashSet<String> valid_strs = new HashSet<String>();
508 valid_strs.add(OAIXML.FROM);
509 valid_strs.add(OAIXML.UNTIL);
510 valid_strs.add(OAIXML.SET);
511 valid_strs.add(OAIXML.METADATA_PREFIX);
512 valid_strs.add(OAIXML.RESUMPTION_TOKEN);
513
514 Document result_doc = XMLConverter.newDOM();
515 Element result_element = result_doc.createElement(verb);
516 boolean result_token_needed = false; // does this result need to include a
517 // resumption token
518
519 NodeList params = GSXML.getChildrenByTagName(req, GSXML.PARAM_ELEM);
520
521 HashMap<String, String> param_map = GSXML.getParamMap(params);
522
523 // are all the params valid?
524 if (!areAllParamsValid(param_map, valid_strs)) {
525 logger.error("One of the params is invalid");
526 return OAIXML.createErrorMessage(OAIXML.BAD_ARGUMENT, "There was an invalid parameter");
527 // TODO, need to tell the user which one was invalid ??
528 }
529
530 // Do we have a resumption token??
531 String token = null;
532 String from = null;
533 String until = null;
534 boolean set_requested = false;
535 String set_spec_str = null;
536 String prefix_value = null;
537 int cursor = 0;
538 int current_cursor = 0;
539 String current_set = null;
540 long initial_time = 0;
541
542 int total_size = -1; // we are only going to set this in resumption
543 // token if it is easy to work out, i.e. not sending extra requests to
544 // MR just to calculate total size
545
546 if(param_map.containsKey(OAIXML.RESUMPTION_TOKEN)) {
547 // Is it an error to have other arguments? Do we need to check to make sure that resumptionToken is the only arg??
548 // validate resumptionToken
549 token = param_map.get(OAIXML.RESUMPTION_TOKEN);
550 logger.info("has resumptionToken " + token);
551 if(OAIResumptionToken.isValidToken(token) == false) {
552 logger.error("token is not valid");
553 return OAIXML.createErrorMessage(OAIXML.BAD_RESUMPTION_TOKEN, "");
554 }
555 result_token_needed = true; // we always need to send a token back if we have started with one. It may be empty if we are returning the end of the list
556 // initialise the request params from the stored token data
557 HashMap<String, String> token_data = OAIResumptionToken.getTokenData(token);
558 from = token_data.get(OAIXML.FROM);
559 until = token_data.get(OAIXML.UNTIL);
560 set_spec_str = token_data.get(OAIXML.SET);
561 if (set_spec_str != null) {
562 set_requested = true;
563 }
564 prefix_value = token_data.get(OAIXML.METADATA_PREFIX);
565 current_set = token_data.get(OAIResumptionToken.CURRENT_SET);
566 try {
567 cursor = Integer.parseInt(token_data.get(OAIXML.CURSOR));
568 cursor = cursor + resume_after; // increment cursor
569 current_cursor = Integer.parseInt(token_data.get(OAIResumptionToken.CURRENT_CURSOR));
570 initial_time = Long.parseLong(token_data.get(OAIResumptionToken.INITIAL_TIME));
571 } catch (NumberFormatException e) {
572 logger.error("tried to parse int from cursor data and failed");
573 }
574
575 // check that the collections/sets haven't changed since the token was issued
576 if (collectionsChangedSinceTime(set_spec_str, initial_time)) {
577 logger.error("one of the collections in set "+set_spec_str+" has changed since token issued. Expiring the token");
578 OAIResumptionToken.expireToken(token);
579 return OAIXML.createErrorMessage(OAIXML.BAD_RESUMPTION_TOKEN, "Repository data has changed since this token was issued. Resend original request");
580 }
581 }
582 else {
583 // no resumption token, lets check the other params
584 // there must be a metadataPrefix
585 if (!param_map.containsKey(OAIXML.METADATA_PREFIX)) {
586 logger.error("metadataPrefix param required");
587 return OAIXML.createErrorMessage(OAIXML.BAD_ARGUMENT, "metadataPrefix param required");
588 }
589
590 //if there are any date params, check they're of the right format
591 Date from_date = null;
592 Date until_date = null;
593
594 from = param_map.get(OAIXML.FROM);
595 if(from != null) {
596 from_date = OAIXML.getDate(from);
597 if(from_date == null) {
598 logger.error("invalid date: " + from);
599 return OAIXML.createErrorMessage(OAIXML.BAD_ARGUMENT, "invalid format for "+ OAIXML.FROM);
600 }
601 }
602 until = param_map.get(OAIXML.UNTIL);
603 if(until != null) {
604 until_date = OAIXML.getDate(until);
605 if(until_date == null) {
606 logger.error("invalid date: " + until);
607 return OAIXML.createErrorMessage(OAIXML.BAD_ARGUMENT, "invalid format for "+ OAIXML.UNTIL);
608 }
609 }
610
611 if(from != null && until != null) { // check they are of the same date-time format (granularity)
612 if(from.length() != until.length()) {
613 logger.error("The request has different granularities (date-time formats) for the From and Until date parameters.");
614 return OAIXML.createErrorMessage(OAIXML.BAD_ARGUMENT, "The request has different granularities (date-time formats) for the From and Until date parameters.");
615 }
616
617 if(from_date.compareTo(until_date) > 0) { // from date can't be later than until date
618 return OAIXML.createErrorMessage(OAIXML.NO_RECORDS_MATCH, "");
619 }
620 }
621
622 if(until_date != null) {
623
624 // Also call until_date.compareTo(earliestdatestamp) as the until date can't precede the earliest timestamp
625 // Unfortunately, this test has to be done after the granularity test
626 // compareTo() returns the value 0 if the argument Date is equal to this Date; a value less than 0 if this Date is before
627 // the Date argument; and a value greater than 0 if this Date is after the Date argument.
628 long earliestDatestamp = getEarliestDateStamp(collection_list);
629 String earliestDatestamp_str = OAIXML.getTime(earliestDatestamp);
630 Date earliestDatestamp_date = OAIXML.getDate(earliestDatestamp_str);
631
632 if(until_date.compareTo(earliestDatestamp_date) < 0) {
633 return OAIXML.createErrorMessage(OAIXML.NO_RECORDS_MATCH, "");
634 }
635 }
636
637
638 // check the set arg is a set we know about
639 set_requested = param_map.containsKey(OAIXML.SET);
640 set_spec_str = null;
641 if(set_requested == true) {
642 set_spec_str = param_map.get(OAIXML.SET);
643 if (!this.set_set.contains(set_spec_str)) {
644 // the set is not one we know about
645 logger.error("requested set is not found in this repository");
646 return OAIXML.createErrorMessage(OAIXML.BAD_ARGUMENT, "invalid set parameter");
647
648 }
649 }
650 // Is the metadataPrefix arg one this repository supports?
651 prefix_value = param_map.get(OAIXML.METADATA_PREFIX);
652 if (repositorySupportsMetadataPrefix(prefix_value) == false) {
653 logger.error("requested metadataPrefix is not found in OAIConfig.xml");
654 return OAIXML.createErrorMessage(OAIXML.CANNOT_DISSEMINATE_FORMAT, "metadata format "+prefix_value+" not supported by this repository");
655 }
656
657 } // else no resumption token, check other params
658
659 // Whew. Now we have validated the params, we can work on doing the actual
660 // request
661
662
663 Document doc = XMLConverter.newDOM();
664 Element mr_msg = doc.createElement(GSXML.MESSAGE_ELEM);
665 Element mr_req = doc.createElement(GSXML.REQUEST_ELEM);
666 // TODO does this need a type???
667 mr_msg.appendChild(mr_req);
668
669 // copy in the from/until params if there
670 if (from != null) {
671 mr_req.appendChild(GSXML.createParameter(doc, OAIXML.FROM, from));
672 }
673 if (until != null) {
674 mr_req.appendChild(GSXML.createParameter(doc, OAIXML.UNTIL, until));
675 }
676 // add metadataPrefix
677 mr_req.appendChild(GSXML.createParameter(doc, OAIXML.METADATA_PREFIX, prefix_value));
678
679 // do we have a set???
680 // if no set, we send to all collections in the collection list
681 // if super set, we send to all collections in super set list
682 // if a single collection, send to it
683 // if a subset, send to the collection
684 Vector<String> current_coll_list = getCollectionListForSet(set_spec_str);
685 if(set_spec_str != null) {
686 logger.info(">>>> Set spec: " + set_spec_str);
687 debugPrintCurrentCollList(current_coll_list, "Before removing any deactivated colls. Set spec: " + set_spec_str);
688 }
689
690 boolean single_collection = false;
691 if (current_coll_list.size() == 1) {
692 single_collection = true;
693
694 // now handle any deactivated OAI collections. OAI collections can be deactivated briefly during build, when the building folder gets moved to index
695 String collName = current_coll_list.get(0); //set_spec_str;
696
697 int index = collection_name_list.indexOf(collName);
698 if(collection_state_list.get(index).equals(DEACTIVATED)) {
699 // forced to send this as an OAI error message, since the XML output is of OAI schema
700 // and has to match http://www.openarchives.org/OAI/2.0/OAI-PMH.xsd, which has no info message
701 //return OAIXML.createCollectionDeactivatedMessage(collName);
702 String errorCode = (record_type.equals(OAIXML.RECORD)) ? OAIXML.NO_RECORDS_MATCH : OAIXML.ID_DOES_NOT_EXIST; // GetRecords vs GetIdentifiers request
703 return OAIXML.createErrorMessage(errorCode, "OAI collection " + collName + " is temporarily inactive. Likely it's in the final stages of being rebuilt. Check back shortly.");
704 }
705 } else {
706
707 debugListActivatedCollections("doListRecordsOrIdentifiers");
708
709 for(int i = 0; i < collection_name_list.size(); i++) {
710 if(collection_state_list.get(i).equals(DEACTIVATED)) {
711 String collName = collection_name_list.get(i);
712 // remove from the list of collections for which we're going to get identifiers/records
713 // so we won't get identifiers/records for any deactivated collections
714 current_coll_list.remove(collName);
715 logger.info("@@@@ collection " + collName + " is still deactivated!");
716 }
717 }
718
719 // Have we 0 active OAI collections?
720 if (current_coll_list.size() == 0) {
721 // 0 collections are active, but (the remaining one) would have been deactivated, not other reason
722 String errorCode = (record_type.equals(OAIXML.RECORD)) ? OAIXML.NO_RECORDS_MATCH : OAIXML.ID_DOES_NOT_EXIST; // GetRecords vs GetIdentifiers request
723 return OAIXML.createErrorMessage(errorCode, "OAI collections temporarily active. Likely because of collection rebuilding. Check back shortly.");
724 }
725
726 debugPrintCurrentCollList(current_coll_list, "After removing any deactivated colls");
727 }
728 if (set_spec_str != null && set_spec_str.indexOf(":") != -1) {
729 // we have a subset - add the set param back in
730 mr_req.appendChild(GSXML.createParameter(doc, OAIXML.SET, set_spec_str));
731 }
732
733 int num_collected_records = 0;
734 int start_point = current_cursor; // may not be 0 if we are using a resumption token
735 String resumption_collection = "";
736 boolean empty_result_token = false; // if we are sending the last part of a list, then the token value will be empty
737
738 // iterate through the list of collections and send the request to each
739
740 int start_coll=0;
741 if (current_set != null) {
742 // we are resuming a previous request, need to locate the first collection
743 for (int i=0; i<current_coll_list.size(); i++) {
744 if (current_set.equals(current_coll_list.get(i))) {
745 start_coll = i;
746 break;
747 }
748 }
749 }
750
751 for (int i=start_coll; i<current_coll_list.size(); i++) {
752 String current_coll = current_coll_list.get(i);
753 mr_req.setAttribute(GSXML.TO_ATT, current_coll+"/"+verb);
754
755 Element result = (Element)mr.process(mr_msg);
756 //logger.info("*** " + verb+ " result for coll "+current_coll);
757 //logger.info("*** " + XMLConverter.getPrettyString(result));
758 if (result == null) {
759 logger.info("message router returns null");
760 // do what??? carry on? fail??
761 return OAIXML.createErrorMessage("Internal service returns null", "");
762 }
763 Element res = (Element)GSXML.getChildByTagName(result, GSXML.RESPONSE_ELEM);
764 if(res == null) {
765 logger.info("response element in xml_result is null");
766 return OAIXML.createErrorMessage("Internal service returns null", "");
767 }
768 NodeList record_list = res.getElementsByTagName(record_type);
769 int num_records = record_list.getLength();
770 if(num_records == 0) {
771 logger.info("message router returns 0 records for coll "+current_coll);
772 continue; // try the next collection
773 }
774 if (single_collection) {
775 total_size = num_records;
776 }
777 int records_to_add = (resume_after > 0 ? resume_after - num_collected_records : num_records);
778 if (records_to_add > (num_records-start_point)) {
779 records_to_add = num_records-start_point;
780 }
781 addRecordsToList(result_doc, result_element, record_list, start_point, records_to_add);
782 num_collected_records += records_to_add;
783
784 // do we need to stop here, and do we need to issue a resumption token?
785 if (resume_after > 0 && num_collected_records == resume_after) {
786 // we have finished collecting records at the moment.
787 // but are we conincidentally at the end? or are there more to go?
788 if (records_to_add < (num_records - start_point)) {
789 // we have added less than this collection had
790 start_point += records_to_add;
791 resumption_collection = current_coll;
792 result_token_needed = true;
793 }
794 else {
795 // we added all this collection had to offer
796 // is there another collection in the list??
797 if (i<current_coll_list.size()-1) {
798 result_token_needed = true;
799 start_point = 0;
800 resumption_collection = current_coll_list.get(i+1);
801 }
802 else {
803 // we have finished one collection and there are no more collection
804 // if we need to send a resumption token (in this case, only because we started with one, then it will be empty
805 logger.info("*** at end of list, need empty result token");
806 empty_result_token = true;
807 }
808 }
809 break;
810 }
811 start_point = 0; // only the first one will have start non-zero, if we
812 // have a resumption token
813
814 } // for each collection
815
816 if (num_collected_records ==0) {
817 // there were no matching results
818 return OAIXML.createErrorMessage(OAIXML.NO_RECORDS_MATCH, "");
819 }
820
821 if (num_collected_records < resume_after) {
822 // we have been through all collections, and there are no more
823 // if we need a result token - only because we started with one, so we need to send an empty one, then make sure everyone knows we are just sending an empty one
824 if (result_token_needed) {
825 empty_result_token = true;
826 }
827 }
828
829 if (result_token_needed) {
830 // we need a resumption token
831 if (empty_result_token) {
832 logger.info("*** have empty result token");
833 token = "";
834 } else {
835 if (token != null) {
836 // we had a token for this request, we can just update it
837 token = OAIResumptionToken.updateToken(token, ""+cursor, resumption_collection, ""+start_point);
838 } else {
839 // we are generating a new one
840 token = OAIResumptionToken.createAndStoreResumptionToken(set_spec_str, prefix_value, from, until, ""+cursor, resumption_collection, ""+start_point );
841 }
842 }
843
844 // result token XML
845 long expiration_date = -1;
846 if (empty_result_token) {
847 // we know how many records in total as we have sent them all
848 total_size = cursor+num_collected_records;
849 } else {
850 // non-empty token, set the expiration date
851 expiration_date = OAIResumptionToken.getExpirationDate(token);
852 }
853 Element token_elem = OAIXML.createResumptionTokenElement(result_doc, token, total_size, cursor, expiration_date);
854 // OAIXML.addToken(token_elem); // store it
855 result_element.appendChild(token_elem); // add to the result
856 }
857
858
859 return getMessage(result_doc, result_element);
860 }
861
862 private Vector<String> getCollectionListForSet(String set) {
863 if (set == null) {
864 // no set requested, need the complete collection list
865
866 // Important to return a clone of the member variable, since collection names can be locally removed from the Vector by
867 // the caller for any collection that is deactivated. This is so the caller can determine which collections are active and
868 // thus have records/identifiers to display. We don't want to remove elements from the member variable collection_name_list.
869 return (Vector<String>)this.collection_name_list.clone();
870 }
871 if (has_super_colls && super_coll_map.containsKey(set)) {
872 return super_coll_map.get(set);
873 }
874
875 Vector<String> coll_list = new Vector<String>();
876 if (set.indexOf(":") != -1) {
877 String col_name = set.substring(0, set.indexOf(":"));
878 coll_list.add(col_name);
879 }
880 else {
881 coll_list.add(set);
882 }
883 return coll_list;
884 }
885 private void addRecordsToList(Document doc, Element result_element, NodeList
886 record_list, int start_point, int num_records) {
887 int end_point = start_point + num_records;
888 for (int i=start_point; i<end_point; i++) {
889 result_element.appendChild(doc.importNode(record_list.item(i), true));
890 }
891 }
892
893 private Element collectAll(Element result, Element msg, String verb, String elem_name) {
894 if(result == null) {
895 //in the first round, result is null
896 return msg;
897 }
898 Element res_in_result = (Element)GSXML.getChildByTagName(result, GSXML.RESPONSE_ELEM);
899 if(res_in_result == null) { // return the results of all other collections accumulated so far
900 return msg;
901 }
902 Element verb_elem = (Element)GSXML.getChildByTagName(res_in_result, verb);
903 if(msg == null) {
904 return result;
905 }
906
907 //e.g., get all <record> elements from the returned message. There may be none of
908 //such element, for example, the collection service returned an error message
909 NodeList elem_list = msg.getElementsByTagName(elem_name);
910
911 for (int i=0; i<elem_list.getLength(); i++) {
912 verb_elem.appendChild(res_in_result.getOwnerDocument().importNode(elem_list.item(i), true));
913 }
914 return result;
915 }
916
917
918 /** there are three possible exception conditions: bad argument, idDoesNotExist, and noMetadataFormat.
919 * The first one is handled here, and the last two are processed by OAIPMH.
920 */
921 private Element doListMetadataFormats(Element req) {
922 //if the verb is ListMetadataFormats, there could be only one parameter: identifier
923 //, or there is no parameter; otherwise it is an error
924 //logger.info("" + XMLConverter.getString(msg));
925
926 NodeList params = GSXML.getChildrenByTagName(req, GSXML.PARAM_ELEM);
927 Element param = null;
928 Document lmf_doc = XMLConverter.newDOM();
929 if(params.getLength() == 0) {
930 //this is requesting metadata formats for the whole repository
931 //read the oaiConfig.xml file, return the metadata formats specified there.
932 if (this.listmetadataformats_response != null) {
933 // we have already created it
934 return this.listmetadataformats_response;
935 }
936
937 Element list_metadata_formats = lmf_doc.createElement(OAIXML.LIST_METADATA_FORMATS);
938 // get all the formats out of oai_config
939 NodeList formats = oai_config.getElementsByTagName(OAIXML.METADATA_FORMAT);
940 if (formats.getLength() ==0) {
941 logger.error("OAIConfig.xml must contain the supported metadata formats");
942 // TODO this is internal error, what to do???
943 return getMessage(lmf_doc, list_metadata_formats);
944 }
945
946 for(int i=0; i<formats.getLength(); i++) {
947 Element f = OAIXML.getMetadataFormatShort(lmf_doc, (Element)formats.item(i));
948 list_metadata_formats.appendChild(f);
949 }
950 this.listmetadataformats_response = getMessage(lmf_doc, list_metadata_formats);
951 return this.listmetadataformats_response;
952
953 }
954
955 if (params.getLength() > 1) {
956 //Bad argument. Can't be more than one parameters for ListMetadataFormats verb
957 return OAIXML.createErrorMessage(OAIXML.BAD_ARGUMENT, "");
958 }
959
960 // This is a request for the metadata of a particular item with an identifier
961 /**the request xml is in the form: <request>
962 * <param name=.../>
963 * </request>
964 *And there is a param element and one element only. (No paramList element in between).
965 */
966 param = (Element)params.item(0);
967 String param_name = param.getAttribute(GSXML.NAME_ATT);
968 String identifier = "";
969 if (!param_name.equals(OAIXML.IDENTIFIER)) {
970 //Bad argument
971 return OAIXML.createErrorMessage(OAIXML.BAD_ARGUMENT, "");
972 }
973
974 identifier = param.getAttribute(GSXML.VALUE_ATT);
975 // the identifier is in the form: <coll_name>:<OID>
976 // so it must contain at least one ':' characters
977 // (the oid itself may contain : chars)
978 String[] strs = identifier.split(":", 2);
979 if(strs.length != 2) {
980 logger.error("identifier is not in the form coll:id" + identifier);
981 return OAIXML.createErrorMessage(OAIXML.ID_DOES_NOT_EXIST, "");
982 }
983
984 // send request to message router
985 // get the names
986 String coll_name = strs[0];
987 String oid = strs[1];
988
989 Document msg_doc = XMLConverter.newDOM();
990 Element message = msg_doc.createElement(GSXML.MESSAGE_ELEM);
991 String verb = req.getAttribute(GSXML.TO_ATT);
992 String new_to = coll_name + "/" + verb;
993 Element request = GSXML.createBasicRequest(msg_doc, "oai???", new_to, null);
994 message.appendChild(request);
995 // add the id param
996 GSXML.addParameterToList(request, OAIXML.OID, oid);
997
998 //Now send the request to the message router to process
999 Node result_node = mr.process(message);
1000 return GSXML.nodeToElement(result_node);
1001 }
1002
1003 private void copyNamedElementfromConfig(Element to_elem, String element_name) {
1004 Element original_element = (Element)GSXML.getChildByTagName(oai_config, element_name);
1005 if(original_element != null) {
1006 GSXML.copyNode(to_elem, original_element);
1007 }
1008 }
1009
1010
1011 private Element doIdentify() {
1012 //The validation for this verb has been done in OAIServer.validate(). So no bother here.
1013 logger.info("");
1014 if (this.identify_response != null) {
1015 // we have already created it
1016 return getMessage(this.identify_response.getOwnerDocument(), this.identify_response);
1017 }
1018 Document doc = XMLConverter.newDOM();
1019 Element identify = doc.createElement(OAIXML.IDENTIFY);
1020 //do the repository name
1021 copyNamedElementfromConfig(identify, OAIXML.REPOSITORY_NAME);
1022 //do the baseurl
1023 copyNamedElementfromConfig(identify, OAIXML.BASE_URL);
1024 //do the protocol version
1025 copyNamedElementfromConfig(identify, OAIXML.PROTOCOL_VERSION);
1026
1027 //There can be more than one admin email according to the OAI specification
1028 NodeList admin_emails = GSXML.getChildrenByTagName(oai_config, OAIXML.ADMIN_EMAIL);
1029 int num_admin = 0;
1030 Element from_admin_email = null;
1031 if (admin_emails != null) {
1032 num_admin = admin_emails.getLength();
1033 }
1034 for (int i=0; i<num_admin; i++) {
1035 GSXML.copyNode(identify, admin_emails.item(i));
1036 }
1037
1038 //do the earliestDatestamp
1039 //send request to mr to search through the earliest datestamp amongst all oai collections in the repository.
1040 //ask the message router for a list of oai collections
1041 //NodeList oai_coll = getOAICollectionList();
1042 long earliestDatestamp = getEarliestDateStamp(collection_list);
1043 String earliestDatestamp_str = OAIXML.getTime(earliestDatestamp);
1044 Element earliestDatestamp_elem = doc.createElement(OAIXML.EARLIEST_DATESTAMP);
1045 GSXML.setNodeText(earliestDatestamp_elem, earliestDatestamp_str);
1046 identify.appendChild(earliestDatestamp_elem);
1047
1048 //do the deletedRecord
1049 copyNamedElementfromConfig(identify, OAIXML.DELETED_RECORD);
1050 //do the granularity
1051 copyNamedElementfromConfig(identify, OAIXML.GRANULARITY);
1052
1053 // output the oai identifier
1054 Element description = doc.createElement(OAIXML.DESCRIPTION);
1055 identify.appendChild(description);
1056 // TODO, make this a valid id
1057 Element oaiIdentifier = OAIXML.createOAIIdentifierXML(doc, repository_id, "lucene-jdbm-demo", "ec159e");
1058 description.appendChild(oaiIdentifier);
1059
1060 // if there are any oaiInfo metadata, add them in too.
1061 Element info = (Element)GSXML.getChildByTagName(oai_config, OAIXML.OAI_INFO);
1062 if (info != null) {
1063 NodeList meta = GSXML.getChildrenByTagName(info, OAIXML.METADATA);
1064 if (meta != null && meta.getLength() > 0) {
1065 Element gsdl = OAIXML.createGSDLElement(doc);
1066 description.appendChild(gsdl);
1067 for (int m = 0; m<meta.getLength(); m++) {
1068 GSXML.copyNode(gsdl, meta.item(m));
1069 }
1070
1071 }
1072 }
1073 this.identify_response = identify;
1074 return getMessage(doc, identify);
1075 }
1076 /** split the identifier into <collection + OID> as an array
1077 It has already been checked that the 'identifier' contains at least one ':'
1078 */
1079
1080 /** validate if the specified metadata prefix value is supported by the repository
1081 * by checking it in the OAIConfig.xml
1082 */
1083 private boolean repositorySupportsMetadataPrefix(String prefix_value) {
1084 NodeList prefix_list = oai_config.getElementsByTagName(OAIXML.METADATA_PREFIX);
1085
1086 for(int i=0; i<prefix_list.getLength(); i++) {
1087 if(prefix_value.equals(GSXML.getNodeText((Element)prefix_list.item(i)).trim() )) {
1088 return true;
1089 }
1090 }
1091 return false;
1092 }
1093 private Element doGetRecord(Element req){
1094 logger.info("");
1095 /** arguments:
1096 identifier: required
1097 metadataPrefix: required
1098 * Exceptions: badArgument; cannotDisseminateFormat; idDoesNotExist
1099 */
1100 Document doc = XMLConverter.newDOM();
1101 Element get_record = doc.createElement(OAIXML.GET_RECORD);
1102
1103 HashSet<String> valid_strs = new HashSet<String>();
1104 valid_strs.add(OAIXML.IDENTIFIER);
1105 valid_strs.add(OAIXML.METADATA_PREFIX);
1106
1107 NodeList params = GSXML.getChildrenByTagName(req, GSXML.PARAM_ELEM);
1108 HashMap<String, String> param_map = GSXML.getParamMap(params);
1109
1110 if(!areAllParamsValid(param_map, valid_strs) ||
1111 params.getLength() == 0 ||
1112 param_map.containsKey(OAIXML.IDENTIFIER) == false ||
1113 param_map.containsKey(OAIXML.METADATA_PREFIX) == false ) {
1114 logger.error("must have the metadataPrefix/identifier parameter.");
1115 return OAIXML.createErrorMessage(OAIXML.BAD_ARGUMENT, "");
1116 }
1117
1118 String prefix = param_map.get(OAIXML.METADATA_PREFIX);
1119 String identifier = param_map.get(OAIXML.IDENTIFIER);
1120
1121 // verify the metadata prefix
1122 if (repositorySupportsMetadataPrefix(prefix) == false) {
1123 logger.error("requested prefix is not found in OAIConfig.xml");
1124 return OAIXML.createErrorMessage(OAIXML.CANNOT_DISSEMINATE_FORMAT, "");
1125 }
1126
1127 // get the names
1128 String[] strs = identifier.split(":", 2);
1129 if(strs == null || strs.length < 2) {
1130 logger.error("identifier is not in the form coll:id" + identifier);
1131 return OAIXML.createErrorMessage(OAIXML.ID_DOES_NOT_EXIST, "");
1132 }
1133 //String name_of_site = strs[0];
1134 String coll_name = strs[0];
1135 String oid = strs[1];
1136
1137 //re-organize the request element
1138 // reset the 'to' attribute
1139 String verb = req.getAttribute(GSXML.TO_ATT);
1140 req.setAttribute(GSXML.TO_ATT, coll_name + "/" + verb);
1141 // reset the identifier element
1142 Element param = GSXML.getNamedElement(req, GSXML.PARAM_ELEM, GSXML.NAME_ATT, OAIXML.IDENTIFIER);
1143 if (param != null) {
1144 param.setAttribute(GSXML.NAME_ATT, OAIXML.OID);
1145 param.setAttribute(GSXML.VALUE_ATT, oid);
1146 }
1147
1148 //Now send the request to the message router to process
1149 Element msg = doc.createElement(GSXML.MESSAGE_ELEM);
1150 msg.appendChild(doc.importNode(req, true));
1151 Node result_node = mr.process(msg);
1152 return GSXML.nodeToElement(result_node);
1153 }
1154
1155 // See OAIConfig.xml
1156 // dynamically works out what the earliestDateStamp is, since it varies by collection
1157 // returns this time in *milliseconds*.
1158 protected long getEarliestDateStamp(Element oai_coll_list) {
1159 // config earliest datstamp
1160 long config_datestamp = 0;
1161 Element config_datestamp_elem = (Element)GSXML.getChildByTagName(this.oai_config, OAIXML.EARLIEST_DATESTAMP);
1162 if (config_datestamp_elem != null) {
1163 String datest = GSXML.getNodeText(config_datestamp_elem);
1164 config_datestamp = OAIXML.getTime(datest);
1165 if (config_datestamp == -1) {
1166 config_datestamp = 0;
1167 }
1168 }
1169 //do the earliestDatestamp
1170 long current_time = System.currentTimeMillis();
1171 long earliestDatestamp = current_time;
1172 NodeList oai_coll = oai_coll_list.getElementsByTagName(GSXML.COLLECTION_ELEM);
1173 int oai_coll_size = oai_coll.getLength();
1174 if (oai_coll_size == 0) {
1175 logger.info("returned oai collection list is empty. Setting repository earliestDatestamp to be the earliest datestamp from OAIConfig.xml, or 1970-01-01 if not specified.");
1176 return config_datestamp;
1177 }
1178 // the earliestDatestamp is now stored as a metadata element in the collection's buildConfig.xml file
1179 // we get the earliestDatestamp among the collections
1180 for(int i=0; i<oai_coll_size; i++) {
1181 String collName = collection_name_list.get(i);
1182 long coll_earliestDatestamp = Long.parseLong(((Element)oai_coll.item(i)).getAttribute(OAIXML.EARLIEST_OAI_DATESTAMP)); // Taken from oai-inf db's OAI_EARLIEST_TIMESTAMP_OID entry, -1 if not found
1183
1184 if (coll_earliestDatestamp > 0 && earliestDatestamp > coll_earliestDatestamp) {
1185 earliestDatestamp = coll_earliestDatestamp;
1186 //logger.info("@@@ Found earlier timestamp: " + earliestDatestamp + " ms");
1187 }
1188 }
1189
1190 // we're no longer trying fallbacks for earliestDatestamp (other than the extreme fallback of
1191 // unix epoch time) because, going forward, all collections will have oai-inf db containing
1192 // an entry for earliesttimestamp. And all OAICollections will moreover have them stored and
1193 // will return them upon calling getEarliestOAIDatestamp().
1194 /*
1195 if(earliestDatestamp == current_time) {
1196 logger.info("Can't determine earliesttimestamp from oai-inf.db for any OAI collection. Trying timestamps in build config...");
1197 for(int i=0; i<oai_coll_size; i++) {
1198 String collName = collection_name_list.get(i);
1199 long coll_earliestDatestamp = Long.parseLong(((Element)oai_coll.item(i)).getAttribute(OAIXML.EARLIEST_DATESTAMP)); // Taken from the earliest datestamp field in buildcfg
1200 if (coll_earliestDatestamp == 0) {
1201 // try last modified
1202 coll_earliestDatestamp = Long.parseLong(((Element)oai_coll.item(i)).getAttribute(OAIXML.LAST_MODIFIED));
1203 //logger.info("@@@ Falling back to using collection " + collName + "'s lastmodified date as its earliest timestamp: " + coll_earliestDatestamp);
1204 }
1205 if (coll_earliestDatestamp > 0) {
1206 earliestDatestamp = (earliestDatestamp > coll_earliestDatestamp)? coll_earliestDatestamp : earliestDatestamp;
1207 }
1208 }
1209 }
1210 */
1211
1212 if (earliestDatestamp == current_time) {
1213 logger.info("no collection had a real datestamp, using value from OAIConfig");
1214 return config_datestamp;
1215 }
1216 return earliestDatestamp;
1217 }
1218
1219 private boolean collectionsChangedSinceTime(String set_spec_str, long initial_time) {
1220
1221 // we need to look though all collections in the set to see if any have last modified dates > initial_time
1222 Vector<String> set_coll_list = getCollectionListForSet(set_spec_str);
1223
1224 Node child = this.collection_list.getFirstChild();
1225 while (child != null) {
1226 if (child.getNodeName().equals(GSXML.COLLECTION_ELEM)) {
1227 String coll_id =((Element) child).getAttribute(GSXML.NAME_ATT);
1228 if (set_coll_list.contains(coll_id)) {
1229 long last_modified = Long.parseLong(((Element)child).getAttribute(OAIXML.LAST_MODIFIED));
1230 if (initial_time < last_modified) {
1231 return true;
1232 }
1233 }
1234 }
1235 child = child.getNextSibling();
1236 }
1237 return false;
1238
1239 }
1240
1241}
1242
1243
Note: See TracBrowser for help on using the repository browser.