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

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

Part 1 of 2 commits to do with getting errorCallBack working on documentEditing for determining when changes have been saved or not to decided whether editableInitStates can finally be overwritten with current (saved) values.

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