Changeset 28857 for main/trunk
- Timestamp:
- 2014-02-27T14:00:16+13:00 (10 years ago)
- File:
-
- 1 edited
Legend:
- Unmodified
- Added
- Removed
-
main/trunk/greenstone3/src/java/org/greenstone/gsdl3/core/OAIReceptionist.java
r27672 r28857 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 1 20 package org.greenstone.gsdl3.core; 2 21 … … 28 47 /** The unique repository identifier */ 29 48 protected String repository_id = null; 30 31 /** container Document to create XML Nodes for requests sent to message router 32 * Not used for response 33 */ 34 protected Document doc=null; 35 49 36 50 /** a converter class to parse XML and create Docs */ 37 51 protected XMLConverter converter=null; … … 46 60 protected ModuleInterface mr = null; 47 61 48 49 62 // Some of the data/responses will not change while the servlet is running, so 50 63 // we can cache them … … 52 65 /** A list of all the collections available to this OAI server */ 53 66 protected NodeList collection_list = null; 54 67 /** a vector of the names, for convenience */ 68 protected Vector<String> collection_name_list = null; 69 /** If this is true, then there are no OAI enabled collections, so can always return noRecordsMatch (after validating the request params) */ 70 protected boolean noRecordsMatch = false; 71 72 /** A set of all known 'sets' */ 73 protected HashSet<String> set_set = null; 74 75 protected boolean has_super_colls = false; 76 /** a hash of super set-> collection list */ 77 protected HashMap<String, Vector<String>> super_coll_map = null; 55 78 /** The identify response */ 56 79 protected Element identify_response = null; 57 80 /** The list set response */ 81 protected Element listsets_response = null; 82 /** the list metadata formats response */ 83 protected Element listmetadataformats_response = null; 84 58 85 public OAIReceptionist() { 59 86 this.converter = new XMLConverter(); 60 this.doc = this.converter.newDOM();61 62 87 } 63 88 … … 87 112 resume_after = getResumeAfter(); 88 113 89 repository_id = getRepositoryId(); 90 collection_list = getOAICollectionList(); 114 repository_id = getRepositoryIdentifier(); 115 if (!configureSetInfo()) { 116 // there are no sets 117 logger.error("No sets (collections) available for OAI"); 118 return false; 119 } 91 120 92 121 //clear out expired resumption tokens stored in OAIResumptionToken.xml 93 OAI XML.init();94 OAI XML.clearExpiredTokens();122 OAIResumptionToken.init(); 123 OAIResumptionToken.clearExpiredTokens(); 95 124 96 125 return true; 97 126 } 127 128 // assuming that sets are static. If collections change then the servlet 129 // should be restarted. 130 private boolean configureSetInfo() { 131 // do we have any super colls listed in web/WEB-INF/classes/OAIConfig.xml? 132 // Will be like 133 // <oaiSuperSet> 134 // <SetSpec>xxx</SetSpec> 135 // <setName>xxx</SetName> 136 // <SetDescription>xxx</setDescription> 137 // </oaiSuperSet> 138 // The super set is listed in OAIConfig, and collections themselves state 139 // whether they are part of the super set or not. 140 NodeList super_coll_list = this.oai_config.getElementsByTagName(OAIXML.OAI_SUPER_SET); 141 HashMap<String, Element> super_coll_data = new HashMap<String, Element>(); 142 if (super_coll_list.getLength() > 0) { 143 this.has_super_colls = true; 144 for (int i=0; i<super_coll_list.getLength(); i++) { 145 Element super_coll = (Element)super_coll_list.item(i); 146 Element set_spec = (Element)GSXML.getChildByTagName(super_coll, OAIXML.SET_SPEC); 147 if (set_spec != null) { 148 String name = GSXML.getNodeText(set_spec); 149 if (!name.equals("")) { 150 super_coll_data.put(name, super_coll); 151 logger.error("adding in super coll "+name); 152 } 153 } 154 } 155 156 if (super_coll_data.size()==0) { 157 this.has_super_colls = false; 158 } 159 } 160 if (this.has_super_colls == true) { 161 this.super_coll_map = new HashMap<String, Vector<String>>(); 162 } 163 this.set_set = new HashSet<String>(); 164 165 // next, we get a list of all the OAI enabled collections 166 // We get this by sending a listSets request to the MR 167 Document doc = this.converter.newDOM(); 168 Element message = doc.createElement(GSXML.MESSAGE_ELEM); 169 170 Element request = GSXML.createBasicRequest(doc, OAIXML.OAI_SET_LIST, "", null); 171 message.appendChild(request); 172 Node msg_node = mr.process(message); 173 174 if (msg_node == null) { 175 logger.error("returned msg_node from mr is null"); 176 return false; 177 } 178 Element resp = (Element)GSXML.getChildByTagName(msg_node, GSXML.RESPONSE_ELEM); 179 Element coll_list = (Element)GSXML.getChildByTagName(resp, GSXML.COLLECTION_ELEM + GSXML.LIST_MODIFIER); 180 if (coll_list == null) { 181 logger.error("coll_list is null"); 182 return false; 183 } 184 185 NodeList list = coll_list.getElementsByTagName(GSXML.COLLECTION_ELEM); 186 int length = list.getLength(); 187 if (length == 0) { 188 logger.error("length is 0"); 189 noRecordsMatch = true; 190 return false; 191 } 192 193 this.collection_list = list; 194 this.collection_name_list = new Vector<String>(); 195 196 Document listsets_doc = this.converter.newDOM(); 197 Element listsets_element = listsets_doc.createElement(OAIXML.LIST_SETS); 198 this.listsets_response = getMessage(listsets_doc, listsets_element); 199 200 // Now, for each collection, get a list of all its sets 201 // might include subsets (classifiers) or super colls 202 // We'll reuse the first message, changing its type and to atts 203 request.setAttribute(GSXML.TYPE_ATT, ""); 204 StringBuffer to = new StringBuffer(); 205 for (int i=0; i<collection_list.getLength(); i++) { 206 if (i!=0) { 207 to.append(','); 208 } 209 String coll_id =((Element) collection_list.item(i)).getAttribute(GSXML.NAME_ATT); 210 logger.error("coll_id = "+coll_id); 211 to.append(coll_id+"/"+OAIXML.LIST_SETS); 212 this.collection_name_list.add(coll_id); 213 } 214 logger.error ("to att = "+to.toString()); 215 request.setAttribute(GSXML.TO_ATT, to.toString()); 216 // send to MR 217 msg_node = mr.process(message); 218 logger.error(this.converter.getPrettyString(msg_node)); 219 NodeList response_list = ((Element)msg_node).getElementsByTagName(GSXML.RESPONSE_ELEM); 220 for (int c=0; c<response_list.getLength(); c++) { 221 // for each collection's response 222 Element response = (Element)response_list.item(c); 223 String coll_name = GSPath.getFirstLink(response.getAttribute(GSXML.FROM_ATT)); 224 logger.error("coll from response "+coll_name); 225 NodeList set_list = response.getElementsByTagName(OAIXML.SET); 226 for (int j=0; j<set_list.getLength(); j++) { 227 // now check if it a super collection 228 Element set = (Element)set_list.item(j); 229 String set_spec = GSXML.getNodeText((Element)GSXML.getChildByTagName(set, OAIXML.SET_SPEC)); 230 logger.error("set spec = "+set_spec); 231 // this may change if we add site name back in 232 // setSpecs will be collname or collname:subset or supercollname 233 if (set_spec.indexOf(":")==-1 && ! set_spec.equals(coll_name)) { 234 // it must be a super coll spec 235 logger.error("found super coll, "+set_spec); 236 // check that it is a valid one from config 237 if (this.has_super_colls == true && super_coll_data.containsKey(set_spec)) { 238 Vector <String> subcolls = this.super_coll_map.get(set_spec); 239 if (subcolls == null) { 240 logger.error("its new!!"); 241 // not in there yet 242 subcolls = new Vector<String>(); 243 this.set_set.add(set_spec); 244 this.super_coll_map.put(set_spec, subcolls); 245 // the first time a supercoll is mentioned, add into the set list 246 logger.error("finding the set info "+this.converter.getPrettyString(super_coll_data.get(set_spec))); 247 listsets_element.appendChild(GSXML.duplicateWithNewName(listsets_doc, super_coll_data.get(set_spec), OAIXML.SET, true)); 248 } 249 // add this collection to the list for the super coll 250 subcolls.add(coll_name); 251 } 252 } else { // its either the coll itself or a subcoll 253 // add in the set 254 listsets_element.appendChild(listsets_doc.importNode(set, true)); 255 this.set_set.add(set_spec); 256 } 257 } // for each set in the collection 258 } // for each OAI enabled collection 259 return true; 260 } 261 98 262 /** process using strings - just calls process using Elements */ 99 263 public String process(String xml_in) { … … 104 268 } 105 269 106 //Compose a message element used to send back to the OAIServer servlet.270 //Compose a message/response element used to send back to the OAIServer servlet. 107 271 //This method is only used within OAIReceptionist 108 private Element getMessage(Element e) { 109 Element msg = OAIXML.createElement(OAIXML.MESSAGE); 110 msg.appendChild(OAIXML.getResponse(e)); 272 private Element getMessage(Document doc, Element e) { 273 Element msg = doc.createElement(GSXML.MESSAGE_ELEM); 274 Element response = doc.createElement(GSXML.RESPONSE_ELEM); 275 msg.appendChild(response); 276 response.appendChild(e); 111 277 return msg; 112 278 } 279 113 280 /** process - produce xml data in response to a request 114 281 * if something goes wrong, it returns null - … … 123 290 if (!message.getTagName().equals(GSXML.MESSAGE_ELEM)) { 124 291 logger.error(" Invalid message. GSDL message should start with <"+GSXML.MESSAGE_ELEM+">, instead it starts with:"+message.getTagName()+"."); 125 return getMessage(OAIXML.createErrorElement(OAIXML.BAD_ARGUMENT, ""));292 return OAIXML.createErrorMessage(OAIXML.BAD_ARGUMENT, "Internal messaging error"); 126 293 } 127 294 … … 130 297 if (request == null) { 131 298 logger.error(" message had no request!"); 132 return getMessage(OAIXML.createErrorElement(OAIXML.BAD_ARGUMENT, ""));299 return OAIXML.createErrorMessage(OAIXML.BAD_ARGUMENT, "Internal messaging error"); 133 300 } 134 301 //At this stage, the value of 'to' attribute of the request must be the 'verb' … … 139 306 } 140 307 if (verb.equals(OAIXML.LIST_METADATA_FORMATS)) { 141 return doListMetadataFormats( message);308 return doListMetadataFormats(request); 142 309 } 143 310 if (verb.equals(OAIXML.LIST_SETS)) { 144 return doListSets(message); 311 // we have composed the list sets response on init 312 // Note this means that list sets never uses resumption tokens 313 return this.listsets_response; 145 314 } 146 315 if (verb.equals(OAIXML.GET_RECORD)) { 147 return doGetRecord( message);316 return doGetRecord(request); 148 317 } 149 318 if (verb.equals(OAIXML.LIST_IDENTIFIERS)) { 150 return doListIdentifiers (message);319 return doListIdentifiersOrRecords(request,OAIXML.LIST_IDENTIFIERS , OAIXML.HEADER); 151 320 } 152 321 if (verb.equals(OAIXML.LIST_RECORDS)) { 153 return doListRecords(message); 154 } 155 return getMessage(OAIXML.createErrorElement("Unexpected things happened", "")); 156 157 } 158 /** send a request to the message router asking for a list of collections that support oai 159 * The type attribute must be changed from 'oaiService' to 'oaiSetList' 160 */ 161 private NodeList getOAICollectionList() { 162 Element message = this.doc.createElement(OAIXML.MESSAGE); 163 Element request = this.doc.createElement(OAIXML.REQUEST); 164 message.appendChild(request); 165 request.setAttribute(OAIXML.TYPE, OAIXML.OAI_SET_LIST); 166 request.setAttribute(OAIXML.TO, ""); 167 Node msg_node = mr.process(message); 168 169 if (msg_node == null) { 170 logger.error("returned msg_node from mr is null"); 171 return null; 172 } 173 Element resp = (Element)GSXML.getChildByTagName(msg_node, OAIXML.RESPONSE); 174 Element coll_list = (Element)GSXML.getChildByTagName(resp, OAIXML.COLLECTION_LIST); 175 if (coll_list == null) { 176 logger.error("coll_list is null"); 177 return null; 178 } 179 //logger.info(GSXML.xmlNodeToString(coll_list)); 180 NodeList list = coll_list.getElementsByTagName(OAIXML.COLLECTION); 181 int length = list.getLength(); 182 if (length == 0) { 183 logger.error("length is 0"); 184 return null; 185 } 186 return list; 187 } 188 /**Exclusively called by doListSets()*/ 189 private void getSets(Element list_sets_elem, NodeList oai_coll, int start_point, int end_point) { 190 for (int i=start_point; i<end_point; i++) { 191 String coll_spec = ((Element)oai_coll.item(i)).getAttribute(OAIXML.NAME); 192 String coll_name = coll_spec.substring(coll_spec.indexOf(":") + 1); 193 Element set = OAIXML.createElement(OAIXML.SET); 194 Element set_spec = OAIXML.createElement(OAIXML.SET_SPEC); 195 GSXML.setNodeText(set_spec, coll_spec); 196 set.appendChild(set_spec); 197 Element set_name = OAIXML.createElement(OAIXML.SET_NAME); 198 GSXML.setNodeText(set_name, coll_name); 199 set.appendChild(set_name); 200 list_sets_elem.appendChild(set); 201 } 202 } 322 return doListIdentifiersOrRecords(request, OAIXML.LIST_RECORDS, OAIXML.RECORD); 323 } 324 // should never get here as verbs were checked in OAIServer 325 return OAIXML.createErrorMessage(OAIXML.BAD_VERB, "Unexpected things happened"); 326 327 } 328 329 203 330 private int getResumeAfter() { 204 331 Element resume_after = (Element)GSXML.getChildByTagName(oai_config, OAIXML.RESUME_AFTER); … … 206 333 return -1; 207 334 } 208 private String getRepositoryId () {209 Element ri = (Element)GSXML.getChildByTagName(oai_config, OAIXML.REPOSITORY_ID );335 private String getRepositoryIdentifier() { 336 Element ri = (Element)GSXML.getChildByTagName(oai_config, OAIXML.REPOSITORY_IDENTIFIER); 210 337 if (ri != null) { 211 338 return GSXML.getNodeText(ri); … … 213 340 return ""; 214 341 } 215 /** method to compose a set element 216 */ 217 private Element doListSets(Element msg){ 218 logger.info(""); 219 // option: resumptionToken 220 // exceptions: badArgument, badResumptionToken, noSetHierarchy 221 Element list_sets_elem = OAIXML.createElement(OAIXML.LIST_SETS); 222 223 int oai_coll_size = collection_list.getLength(); 224 if (oai_coll_size == 0) { 225 return getMessage(list_sets_elem); 226 } 227 228 Element req = (Element)GSXML.getChildByTagName(msg, GSXML.REQUEST_ELEM); 229 if (req == null) { 230 logger.error("req is null"); 231 return null; 232 } 233 //params list only contains the parameters other than the verb 234 NodeList params = GSXML.getChildrenByTagName(req, OAIXML.PARAM); 235 Element param = null; 236 int smaller = (oai_coll_size>resume_after)? resume_after : oai_coll_size; 237 if (params.getLength() > 1) { 238 //Bad argument. Can't be more than one parameters for ListMetadataFormats verb 239 return getMessage(OAIXML.createErrorElement(OAIXML.BAD_ARGUMENT, "")); 240 } 241 if(params.getLength() == 0) { 242 //this is requesting a list of sets in the whole repository 243 /** there is no resumeptionToken in the request, we check whether we need 244 * to send out resumeptionToken by comparing the total number of sets in this 245 * repository and the specified value of resumeAfter 246 */ 247 if(resume_after < 0 || oai_coll_size <= resume_after) { 248 //send the whole list of records 249 //all data are sent on the first request. Therefore there should be 250 //no resumeptionToken stored in OAIConfig.xml. 251 //As long as the verb is 'ListSets', we ignore the rest of the parameters 252 getSets(list_sets_elem, collection_list, 0, oai_coll_size); 253 return getMessage(list_sets_elem); 254 } 255 256 //append required sets to list_sets_elem (may be a complete or incomplete list) 257 getSets(list_sets_elem, collection_list, 0, smaller); 258 259 if(oai_coll_size > resume_after) { 260 //An incomplete list is sent; append a resumptionToken element 261 Element token = createResumptionTokenElement(oai_coll_size, 0, resume_after, true); 262 //store this token 263 OAIXML.addToken(token); 264 265 list_sets_elem.appendChild(token); 266 } 267 268 return getMessage(list_sets_elem); 269 } 270 271 // The url should contain only one param called resumptionToken 272 // This is requesting a subsequent part of a list, following a previously sent incomplete list 273 param = (Element)params.item(0); 274 String param_name = param.getAttribute(OAIXML.NAME); 275 if (!param_name.equals(OAIXML.RESUMPTION_TOKEN)) { 276 //Bad argument 277 return getMessage(OAIXML.createErrorElement(OAIXML.BAD_ARGUMENT, "")); 278 } 279 //get the token 280 String token = param.getAttribute(OAIXML.VALUE); 281 //validate the token string (the string has already been decoded in OAIServer, e.g., 282 // replace %3A with ':') 283 if(OAIXML.containsToken(token) == false) { 284 return getMessage(OAIXML.createErrorElement(OAIXML.BAD_RESUMPTION_TOKEN, "")); 285 } 286 //take out the cursor value, which is the size of previously sent list 287 int index = token.indexOf(":"); 288 int cursor = Integer.parseInt(token.substring(index + 1)); 289 Element token_elem = null; 290 291 // are we sending the final part of a complete list? 292 if(cursor + resume_after >= oai_coll_size) { 293 //Yes, we are. 294 //append required sets to list_sets_elem (list is complete) 295 getSets(list_sets_elem, collection_list, cursor, oai_coll_size); 296 //An incomplete list is sent; append a resumptionToken element 297 token_elem = createResumptionTokenElement(oai_coll_size, cursor, -1, false); 298 list_sets_elem.appendChild(token_elem); 299 } else { 300 //No, we are not. 301 //append required sets to list_sets_elem (list is incomplete) 302 getSets(list_sets_elem, collection_list, cursor, cursor + resume_after); 303 token_elem = createResumptionTokenElement(oai_coll_size, cursor, cursor + resume_after, true); 304 //store this token 305 OAIXML.addToken(token_elem); 306 list_sets_elem.appendChild(token_elem); 307 } 308 return getMessage(list_sets_elem); 309 } 310 private Element createResumptionTokenElement(int total_size, int cursor, int so_far_sent, boolean set_expiration, String metadata_prefix) { 311 Element token = OAIXML.createElement(OAIXML.RESUMPTION_TOKEN); 312 token.setAttribute(OAIXML.COMPLETE_LIST_SIZE, "" + total_size); 313 token.setAttribute(OAIXML.CURSOR, "" + cursor); 314 315 if(set_expiration) { 316 /** read the resumptionTokenExpiration element in OAIConfig.xml and get the specified time value 317 * Use the time value plus the current system time to get the expiration date string. 318 */ 319 String expiration_date = OAIXML.getTime(System.currentTimeMillis() + OAIXML.getTokenExpiration()); // in milliseconds 320 token.setAttribute(OAIXML.EXPIRATION_DATE, expiration_date); 321 } 322 323 if(so_far_sent > 0) { 324 //the format of resumptionToken is not defined by the OAI-PMH and should be 325 //considered opaque by the harvester (in other words, strictly follow what the 326 //data provider has to offer 327 //Here, we make use of the uniqueness of the system time 328 String tokenValue = OAIXML.GS3OAI + System.currentTimeMillis() + ":" + so_far_sent; 329 if(!metadata_prefix.equals("")) { 330 tokenValue = tokenValue + ":" + metadata_prefix; 331 } 332 GSXML.setNodeText(token, tokenValue); 333 } 334 return token; 335 } 336 337 private Element createResumptionTokenElement(int total_size, int cursor, int so_far_sent, boolean set_expiration) { 338 return createResumptionTokenElement(total_size, cursor, so_far_sent, set_expiration, ""); // empty metadata_prefix 339 } 342 340 343 341 344 /** if the param_map contains strings other than those in valid_strs, return false; 342 345 * otherwise true. 343 346 */ 344 private boolean isValidParam(HashMap<String, String> param_map, HashSet<String> valid_strs) {347 private boolean areAllParamsValid(HashMap<String, String> param_map, HashSet<String> valid_strs) { 345 348 ArrayList<String> param_list = new ArrayList<String>(param_map.keySet()); 346 349 for(int i=0; i<param_list.size(); i++) { 350 logger.error("param, key = "+param_list.get(i)+", value = "+param_map.get(param_list.get(i))); 347 351 if (valid_strs.contains(param_list.get(i)) == false) { 348 352 return false; … … 351 355 return true; 352 356 } 353 private Element doListIdentifiers(Element msg) { 354 // option: from, until, set, metadataPrefix, resumptionToken 357 358 private Element doListIdentifiersOrRecords(Element req, String verb, String record_type) { 359 // options: from, until, set, metadataPrefix, resumptionToken 355 360 // exceptions: badArgument, badResumptionToken, cannotDisseminateFormat, noRecordMatch, and noSetHierarchy 356 361 HashSet<String> valid_strs = new HashSet<String>(); … … 360 365 valid_strs.add(OAIXML.METADATA_PREFIX); 361 366 valid_strs.add(OAIXML.RESUMPTION_TOKEN); 362 363 Element list_identifiers = OAIXML.createElement(OAIXML.LIST_IDENTIFIERS); 364 Element req = (Element)GSXML.getChildByTagName(msg, GSXML.REQUEST_ELEM); 365 if (req == null) { logger.error("req is null"); return null; } 366 NodeList params = GSXML.getChildrenByTagName(req, OAIXML.PARAM); 367 String coll_name = ""; 368 String token = ""; 369 370 HashMap<String, String> param_map = OAIXML.getParamMap(params); 371 if (!isValidParam(param_map, valid_strs)) { 372 logger.error("One of the params is invalid"); 373 return getMessage(OAIXML.createErrorElement(OAIXML.BAD_ARGUMENT, "")); 374 } 375 // param keys are valid, but if there are any date params, check they're of the right format 376 String from = param_map.get(OAIXML.FROM); 377 if(from != null) { 367 368 Document result_doc = this.converter.newDOM(); 369 Element result_element = result_doc.createElement(verb); 370 boolean result_token_needed = false; // does this result need to include a 371 // resumption token 372 373 NodeList params = GSXML.getChildrenByTagName(req, GSXML.PARAM_ELEM); 374 375 HashMap<String, String> param_map = GSXML.getParamMap(params); 376 377 // are all the params valid? 378 if (!areAllParamsValid(param_map, valid_strs)) { 379 logger.error("One of the params is invalid"); 380 return OAIXML.createErrorMessage(OAIXML.BAD_ARGUMENT, "There was an invalid parameter"); 381 // TODO, need to tell the user which one was invalid ?? 382 } 383 384 // Do we have a resumption token?? 385 String token = null; 386 String from = null; 387 String until = null; 388 boolean set_requested = false; 389 String set_spec_str = null; 390 String prefix_value = null; 391 int cursor = 0; 392 int current_cursor = 0; 393 String current_set = null; 394 395 int total_size = -1; // we are only going to set this in resumption 396 // token if it is easy to work out, i.e. not sending extra requests to 397 // MR just to calculate total size 398 399 if(param_map.containsKey(OAIXML.RESUMPTION_TOKEN)) { 400 // Is it an error to have other arguments? Do we need to check to make sure that resumptionToken is the only arg?? 401 // validate resumptionToken 402 token = param_map.get(OAIXML.RESUMPTION_TOKEN); 403 logger.info("has resumptionToken " + token); 404 if(OAIResumptionToken.isValidToken(token) == false) { 405 logger.error("token is not valid"); 406 return OAIXML.createErrorMessage(OAIXML.BAD_RESUMPTION_TOKEN, ""); 407 } 408 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 409 // initialise the request params from the stored token data 410 HashMap<String, String> token_data = OAIResumptionToken.getTokenData(token); 411 from = token_data.get(OAIXML.FROM); 412 until = token_data.get(OAIXML.UNTIL); 413 set_spec_str = token_data.get(OAIXML.SET); 414 if (set_spec_str != null) { 415 set_requested = true; 416 } 417 prefix_value = token_data.get(OAIXML.METADATA_PREFIX); 418 current_set = token_data.get(OAIResumptionToken.CURRENT_SET); 419 try { 420 cursor = Integer.parseInt(token_data.get(OAIXML.CURSOR)); 421 cursor = cursor + resume_after; // increment cursor 422 current_cursor = Integer.parseInt(token_data.get(OAIResumptionToken.CURRENT_CURSOR)); 423 } catch (NumberFormatException e) { 424 logger.error("tried to parse int from cursor data and failed"); 425 } 426 427 } 428 else { 429 // no resumption token, lets check the other params 430 // there must be a metadataPrefix 431 if (!param_map.containsKey(OAIXML.METADATA_PREFIX)) { 432 logger.error("metadataPrefix param required"); 433 return OAIXML.createErrorMessage(OAIXML.BAD_ARGUMENT, "metadataPrefix param required"); 434 } 435 436 //if there are any date params, check they're of the right format 437 from = param_map.get(OAIXML.FROM); 438 if(from != null) { 378 439 Date from_date = OAIXML.getDate(from); 379 440 if(from_date == null) { 380 381 return getMessage(OAIXML.createErrorElement(OAIXML.BAD_ARGUMENT, ""));441 logger.error("invalid date: " + from); 442 return OAIXML.createErrorMessage(OAIXML.BAD_ARGUMENT, "invalid format for "+ OAIXML.FROM); 382 443 } 383 }384 Stringuntil = param_map.get(OAIXML.UNTIL);385 if(until != null) {444 } 445 until = param_map.get(OAIXML.UNTIL); 446 if(until != null) { 386 447 Date until_date = OAIXML.getDate(until); 387 448 if(until_date == null) { 388 389 return getMessage(OAIXML.createErrorElement(OAIXML.BAD_ARGUMENT, ""));449 logger.error("invalid date: " + until); 450 return OAIXML.createErrorMessage(OAIXML.BAD_ARGUMENT, "invalid format for "+ OAIXML.UNTIL); 390 451 } 391 }392 if(from != null && until != null) { // check they are of the same date-time format (granularity)452 } 453 if(from != null && until != null) { // check they are of the same date-time format (granularity) 393 454 if(from.length() != until.length()) { 394 395 return getMessage(OAIXML.createErrorElement(OAIXML.BAD_ARGUMENT, ""));455 logger.error("The request has different granularities (date-time formats) for the From and Until date parameters."); 456 return OAIXML.createErrorMessage(OAIXML.BAD_ARGUMENT, "The request has different granularities (date-time formats) for the From and Until date parameters."); 396 457 } 397 } 398 399 //ask the message router for a list of oai collections 400 //NodeList oai_coll = collection_list; //getOAICollectionList(); 401 int oai_coll_size = collection_list.getLength(); 402 if (oai_coll_size == 0) { 403 logger.info("returned oai collection list is empty"); 404 return getMessage(OAIXML.createErrorElement(OAIXML.NO_RECORDS_MATCH, "")); 405 } 406 407 //Now we check if the optional argument 'set' has been specified in the params; if so, 408 //whether the specified setSpec is supported by this repository 409 boolean request_set = param_map.containsKey(OAIXML.SET); 410 if(request_set == true) { 411 boolean set_supported = false; 412 String set_spec_str = param_map.get(OAIXML.SET); 413 // get the collection name 414 //if setSpec is supported by this repository, it must be in the form: site_name:coll_name 415 String[] strs = splitSetSpec(set_spec_str); 416 coll_name = strs[1]; 417 418 for(int i=0; i<oai_coll_size; i++) { 419 if(set_spec_str.equals(((Element)collection_list.item(i)).getAttribute(OAIXML.NAME))) { 420 set_supported = true; 421 } 422 } 423 if(set_supported == false) { 424 logger.error("requested set is not found in this repository"); 425 return getMessage(OAIXML.createErrorElement(OAIXML.BAD_ARGUMENT, "")); 426 } 427 } 428 429 //Is there a resumptionToken included which is requesting an incomplete list? 430 if(param_map.containsKey(OAIXML.RESUMPTION_TOKEN)) { 431 // validate resumptionToken 432 token = param_map.get(OAIXML.RESUMPTION_TOKEN); 433 logger.info("has resumptionToken" + token); 434 if(OAIXML.containsToken(token) == false) { 435 return getMessage(OAIXML.createErrorElement(OAIXML.BAD_RESUMPTION_TOKEN, "")); 436 } 437 } 438 439 // Custom test that expects a metadataPrefix comes here at end so that the official params can 440 // be tested first for errors and their error responses sent off. Required for OAI validation 441 if (!param_map.containsKey(OAIXML.METADATA_PREFIX)) { 442 logger.error("contains invalid params or no metadataPrefix"); 443 return getMessage(OAIXML.createErrorElement(OAIXML.BAD_ARGUMENT, "")); 444 } 445 446 //Now that we got a prefix, check and see if it's supported by this repository 447 String prefix_value = param_map.get(OAIXML.METADATA_PREFIX); 448 if (containsMetadataPrefix(prefix_value) == false) { 449 logger.error("requested prefix is not found in OAIConfig.xml"); 450 return getMessage(OAIXML.createErrorElement(OAIXML.CANNOT_DISSEMINATE_FORMAT, "")); 451 } 452 453 //Now that all validation has been done, I hope, we can send request to the message router 454 Element result = null; 455 String verb = req.getAttribute(OAIXML.TO); 456 NodeList param_list = req.getElementsByTagName(OAIXML.PARAM); 457 ArrayList<Element> retain_param_list = new ArrayList<Element>(); 458 for (int j=0; j<param_list.getLength(); j++) { 459 Element e = OAIXML.duplicateElement(msg.getOwnerDocument(), (Element)param_list.item(j), true); 460 retain_param_list.add(e); 461 } 462 463 //re-organize the request element 464 // reset the 'to' attribute 465 if (request_set == false) { 466 logger.info("requesting identifiers of all collections"); 467 for(int i=0; i<oai_coll_size; i++) { 468 if(req == null) { 469 req = msg.getOwnerDocument().createElement(GSXML.REQUEST_ELEM); 470 msg.appendChild(req); 471 for (int j=0; j<retain_param_list.size(); j++) { 472 req.appendChild(retain_param_list.get(j)); 473 } 474 } 475 String full_name = ((Element)collection_list.item(i)).getAttribute(OAIXML.NAME); 476 coll_name = full_name.substring(full_name.indexOf(":") + 1); 477 req.setAttribute(OAIXML.TO, coll_name + "/" + verb); 478 Node n = mr.process(msg); 479 Element e = converter.nodeToElement(n); 480 result = collectAll(result, e, verb, OAIXML.HEADER); 481 482 //clear the content of the old request element 483 msg.removeChild(req); 484 req = null; 485 } 486 } else { 487 req.setAttribute(OAIXML.TO, coll_name + "/" + verb); 488 Node result_node = mr.process(msg); 489 result = converter.nodeToElement(result_node); 490 } 491 492 if (result == null) { 493 logger.info("message router returns null"); 494 return getMessage(OAIXML.createErrorElement("Internal service returns null", "")); 495 } 496 Element res = (Element)GSXML.getChildByTagName(result, OAIXML.RESPONSE); 458 } 459 460 // check the set arg is a set we know about 461 set_requested = param_map.containsKey(OAIXML.SET); 462 set_spec_str = null; 463 if(set_requested == true) { 464 set_spec_str = param_map.get(OAIXML.SET); 465 if (!this.set_set.contains(set_spec_str)) { 466 // the set is not one we know about 467 logger.error("requested set is not found in this repository"); 468 return OAIXML.createErrorMessage(OAIXML.BAD_ARGUMENT, "invalid set parameter"); 469 470 } 471 } 472 // Is the metadataPrefix arg one this repository supports? 473 prefix_value = param_map.get(OAIXML.METADATA_PREFIX); 474 if (repositorySupportsMetadataPrefix(prefix_value) == false) { 475 logger.error("requested metadataPrefix is not found in OAIConfig.xml"); 476 return OAIXML.createErrorMessage(OAIXML.CANNOT_DISSEMINATE_FORMAT, "metadata format "+prefix_value+" not supported by this repository"); 477 } 478 479 } // else no resumption token, check other params 480 481 // Whew. Now we have validated the params, we can work on doing the actual 482 // request 483 484 485 Document doc = this.converter.newDOM(); 486 Element mr_msg = doc.createElement(GSXML.MESSAGE_ELEM); 487 Element mr_req = doc.createElement(GSXML.REQUEST_ELEM); 488 // TODO does this need a type??? 489 mr_msg.appendChild(mr_req); 490 491 // copy in the from/until params if there 492 if (from != null) { 493 mr_req.appendChild(GSXML.createParameter(doc, OAIXML.FROM, from)); 494 } 495 if (until != null) { 496 mr_req.appendChild(GSXML.createParameter(doc, OAIXML.UNTIL, until)); 497 } 498 // add metadataPrefix 499 mr_req.appendChild(GSXML.createParameter(doc, OAIXML.METADATA_PREFIX, prefix_value)); 500 501 // do we have a set??? 502 // if no set, we send to all collections in the collection list 503 // if super set, we send to all collections in super set list 504 // if a single collection, send to it 505 // if a subset, send to the collection 506 Vector<String> current_coll_list = null; 507 boolean single_collection = false; 508 if (set_requested == false) { 509 // just do all colls 510 current_coll_list = collection_name_list; 511 } 512 else if (has_super_colls && super_coll_map.containsKey(set_spec_str)) { 513 current_coll_list = super_coll_map.get(set_spec_str); 514 } 515 else { 516 current_coll_list = new Vector<String>(); 517 if (set_spec_str.indexOf(":") != -1) { 518 // we have a subset 519 //add the set param back into the request, but send the request to the collection 520 String col_name = set_spec_str.substring(0, set_spec_str.indexOf(":")); 521 current_coll_list.add(col_name); 522 mr_req.appendChild(GSXML.createParameter(doc, OAIXML.SET, set_spec_str)); 523 single_collection = true; 524 } 525 else { 526 // it must be a single collection name 527 current_coll_list.add(set_spec_str); 528 single_collection = true; 529 } 530 } 531 532 int num_collected_records = 0; 533 int start_point = current_cursor; // may not be 0 if we are using a resumption token 534 String resumption_collection = ""; 535 boolean empty_result_token = false; // if we are sending the last part of a list, then the token value will be empty 536 537 // iterate through the list of collections and send the request to each 538 539 int start_coll=0; 540 if (current_set != null) { 541 // we are resuming a previous request, need to locate the first collection 542 for (int i=0; i<current_coll_list.size(); i++) { 543 if (current_set.equals(current_coll_list.get(i))) { 544 start_coll = i; 545 break; 546 } 547 } 548 } 549 550 for (int i=start_coll; i<current_coll_list.size(); i++) { 551 String current_coll = current_coll_list.get(i); 552 mr_req.setAttribute(GSXML.TO_ATT, current_coll+"/"+verb); 553 554 Element result = (Element)mr.process(mr_msg); 555 logger.error(verb+ " result for coll "+current_coll); 556 logger.error(this.converter.getPrettyString(result)); 557 if (result == null) { 558 logger.info("message router returns null"); 559 // do what??? carry on? fail?? 560 return OAIXML.createErrorMessage("Internal service returns null", ""); 561 } 562 Element res = (Element)GSXML.getChildByTagName(result, GSXML.RESPONSE_ELEM); 497 563 if(res == null) { 498 564 logger.info("response element in xml_result is null"); 499 return getMessage(OAIXML.createErrorElement("Internal service returns null", "")); 500 } 501 NodeList header_list = res.getElementsByTagName(OAIXML.HEADER); 502 int num_headers = header_list.getLength(); 503 if(num_headers == 0) { 504 logger.info("message router returns 0 headers."); 505 return getMessage(OAIXML.createErrorElement(OAIXML.NO_RECORDS_MATCH, "")); 506 } 507 508 //The request coming in does not contain a token, but we have to check the resume_after value and see if we need to issue a resumption token and 509 // save the token as well. 510 if (token.equals("") == true) { 511 if(resume_after < 0 || num_headers <= resume_after) { 512 //send the whole list of records 513 return result; 514 } 565 return OAIXML.createErrorMessage("Internal service returns null", ""); 566 } 567 NodeList record_list = res.getElementsByTagName(record_type); 568 int num_records = record_list.getLength(); 569 if(num_records == 0) { 570 logger.info("message router returns 0 records for coll "+current_coll); 571 continue; // try the next collection 572 } 573 if (single_collection) { 574 total_size = num_records; 575 } 576 int records_to_add = (resume_after > 0 ? resume_after - num_collected_records : num_records); 577 if (records_to_add > (num_records-start_point)) { 578 records_to_add = num_records-start_point; 579 } 580 addRecordsToList(result_doc, result_element, record_list, start_point, records_to_add); 581 num_collected_records += records_to_add; 515 582 516 //append required number of records (may be a complete or incomplete list) 517 getRecords(list_identifiers, header_list, 0, resume_after); 518 //An incomplete list is sent; append a resumptionToken element 519 Element token_elem = createResumptionTokenElement(num_headers, 0, resume_after, true); 520 //store this token 521 OAIXML.addToken(token_elem); 522 523 list_identifiers.appendChild(token_elem); 524 return getMessage(list_identifiers); 525 } 526 527 if (token.equals("") == false) { 528 //get an appropriate number of records (partial list) according to the token 529 //take out the cursor value, which is the size of previously sent list 530 int index = token.indexOf(":"); 531 int cursor = Integer.parseInt(token.substring(index + 1)); 532 Element token_elem = null; 583 // do we need to stop here, and do we need to issue a resumption token? 584 if (resume_after > 0 && num_collected_records == resume_after) { 585 // we have finished collecting records at the moment. 586 // but are we conincidentally at the end? or are there more to go? 587 if (records_to_add < (num_records - start_point)) { 588 // we have added less than this collection had 589 start_point += records_to_add; 590 resumption_collection = current_coll; 591 result_token_needed = true; 592 } 593 else { 594 // we added all this collection had to offer 595 // is there another collection in the list?? 596 if (i<current_coll_list.size()-1) { 597 result_token_needed = true; 598 start_point = 0; 599 resumption_collection = current_coll_list.get(i+1); 600 } 601 else { 602 // we have finished one collection and there are no more collection 603 // if we need to send a resumption token (in this case, only because we started with one, then it will be empty 604 logger.error("at end of list, need empty result token"); 605 empty_result_token = true; 606 } 607 } 608 break; 609 } 610 start_point = 0; // only the first one will have start non-zero, if we 611 // have a resumption token 533 612 534 // are we sending the final part of a complete list? 535 if(cursor + resume_after >= num_headers) { 536 //Yes, we are. 537 //append required records to list_records (list is complete) 538 getRecords(list_identifiers, header_list, cursor, num_headers); 539 //An incomplete list is sent; append a resumptionToken element 540 token_elem = createResumptionTokenElement(num_headers, cursor, -1, false); 541 list_identifiers.appendChild(token_elem); 613 } // for each collection 614 615 if (num_collected_records ==0) { 616 // there were no matching results 617 return OAIXML.createErrorMessage(OAIXML.NO_RECORDS_MATCH, ""); 618 } 619 620 if (num_collected_records < resume_after) { 621 // we have been through all collections, and there are no more 622 // 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 623 if (result_token_needed) { 624 empty_result_token = true; 625 } 626 } 627 628 if (result_token_needed) { 629 // we need a resumption token 630 if (empty_result_token) { 631 logger.error("have empty result token"); 632 token = ""; 542 633 } else { 543 //No, we are not. 544 //append required records to list_records (list is incomplete) 545 getRecords(list_identifiers, header_list, cursor, cursor + resume_after); 546 token_elem = createResumptionTokenElement(num_headers, cursor, cursor + resume_after, true); 547 //store this token 548 OAIXML.addToken(token_elem); 549 list_identifiers.appendChild(token_elem); 550 } 551 552 return getMessage(list_identifiers); 553 }//end of if(!token.equals("")) 554 555 return result; 556 } 557 private Element doListRecords(Element msg){ 558 logger.info(""); 559 // option: from, until, set, metadataPrefix, and resumptionToken 560 // exceptions: badArgument, badResumptionToken, cannotDisseminateFormat, noRecordMatch, and noSetHierarchy 561 HashSet<String> valid_strs = new HashSet<String>(); 562 valid_strs.add(OAIXML.FROM); 563 valid_strs.add(OAIXML.UNTIL); 564 valid_strs.add(OAIXML.SET); 565 valid_strs.add(OAIXML.METADATA_PREFIX); 566 valid_strs.add(OAIXML.RESUMPTION_TOKEN); 567 568 Element list_records = OAIXML.createElement(OAIXML.LIST_RECORDS); 569 Element req = (Element)GSXML.getChildByTagName(msg, GSXML.REQUEST_ELEM); 570 if (req == null) { logger.error("req is null"); return null; } 571 NodeList params = GSXML.getChildrenByTagName(req, OAIXML.PARAM); 572 573 String coll_name = ""; 574 String token = ""; 575 576 if(params.getLength() == 0) { 577 logger.error("must at least have the metadataPrefix parameter, can't be none"); 578 return getMessage(OAIXML.createErrorElement(OAIXML.BAD_ARGUMENT, "")); 579 } 580 581 HashMap<String, String> param_map = OAIXML.getParamMap(params); 582 if (!isValidParam(param_map, valid_strs)) { 583 logger.error("One of the params is invalid"); 584 return getMessage(OAIXML.createErrorElement(OAIXML.BAD_ARGUMENT, "")); 585 } 586 // param keys are valid, but if there are any date params, check they're of the right format 587 String from = param_map.get(OAIXML.FROM); 588 if(from != null) { 589 Date from_date = OAIXML.getDate(from); 590 if(from_date == null) { 591 logger.error("invalid date: " + from); 592 return getMessage(OAIXML.createErrorElement(OAIXML.BAD_ARGUMENT, "")); 634 if (token != null) { 635 // we had a token for this request, we can just update it 636 token = OAIResumptionToken.updateToken(token, ""+cursor, resumption_collection, ""+start_point); 637 } else { 638 // we are generating a new one 639 token = OAIResumptionToken.createAndStoreResumptionToken(set_spec_str, prefix_value, from, until, ""+cursor, resumption_collection, ""+start_point ); 593 640 } 594 } 595 String until = param_map.get(OAIXML.UNTIL); 596 Date until_date = null; 597 if(until != null) { 598 until_date = OAIXML.getDate(until); 599 if(until_date == null) { 600 logger.error("invalid date: " + until); 601 return getMessage(OAIXML.createErrorElement(OAIXML.BAD_ARGUMENT, "")); 602 } 603 } 604 if(from != null && until != null) { // check they are of the same date-time format (granularity) 605 if(from.length() != until.length()) { 606 logger.error("The request has different granularities (date-time formats) for the From and Until date parameters."); 607 return getMessage(OAIXML.createErrorElement(OAIXML.BAD_ARGUMENT, "")); 608 } 609 } 610 611 //ask the message router for a list of oai collections 612 //NodeList oai_coll = getOAICollectionList(); 613 int oai_coll_size = collection_list.getLength(); 614 if (oai_coll_size == 0) { 615 logger.info("returned oai collection list is empty"); 616 return getMessage(OAIXML.createErrorElement(OAIXML.NO_RECORDS_MATCH, "")); 617 } 618 619 //Now we check if the optional argument 'set' has been specified in the params; if so, 620 //whether the specified setSpec is supported by this repository 621 boolean request_set = param_map.containsKey(OAIXML.SET); 622 if(request_set == true) { 623 boolean set_supported = false; 624 String set_spec_str = param_map.get(OAIXML.SET); 625 // get the collection name 626 //if setSpec is supported by this repository, it must be in the form: site_name:coll_name 627 String[] strs = splitSetSpec(set_spec_str); 628 // name_of_site = strs[0]; 629 coll_name = strs[1]; 630 //logger.info("param contains set: "+coll_name); 631 632 for(int i=0; i<oai_coll_size; i++) { 633 if(set_spec_str.equals(((Element)collection_list.item(i)).getAttribute(OAIXML.NAME))) { 634 set_supported = true; 635 } 636 } 637 if(set_supported == false) { 638 logger.error("requested set is not found in this repository"); 639 return getMessage(OAIXML.createErrorElement(OAIXML.BAD_ARGUMENT, "")); 640 } 641 } 642 643 //Is there a resumptionToken included which is requesting an incomplete list? 644 if(param_map.containsKey(OAIXML.RESUMPTION_TOKEN)) { 645 // validate resumptionToken 646 //if (the token value is not found in the token xml file) { 647 // return getMessage(OAIXML.createErrorElement(OAIXML.BAD_RESUMPTION_TOKEN, "")); 648 //} else { 649 // use the request to get a complete list of records from the message router 650 // and issue the subsequent part of that complete list according to the token. 651 // store a new token if necessary. 652 //} 653 token = param_map.get(OAIXML.RESUMPTION_TOKEN); 654 logger.info("has resumptionToken: " + token); 655 if(OAIXML.containsToken(token) == false) { 656 return getMessage(OAIXML.createErrorElement(OAIXML.BAD_RESUMPTION_TOKEN, "")); 657 } 658 } 659 660 // Moved the additional custom test that mandates the metadataPrefix here, since official 661 // errors should be caught first, so that their error responses can be sent off first 662 // such that GS2's oaiserver will validate properly. 663 if (!param_map.containsKey(OAIXML.METADATA_PREFIX)) { 664 if(!token.equals("")) { // resumptiontoken 665 int lastIndex = token.lastIndexOf(":"); 666 if(lastIndex != token.indexOf(":")) { // if a meta_prefix is suffixed to the usual token, 667 // put that in the map and remove it from the end of the stored token 668 String meta_prefix = token.substring(lastIndex+1); 669 param_map.put(OAIXML.METADATA_PREFIX, meta_prefix); 670 token = token.substring(0, lastIndex); 671 param_map.put(OAIXML.RESUMPTION_TOKEN, token); 672 673 // Add to request <param name="metadataPrefix" value="oai_dc"/> 674 // need to add metaprefix as param to request, else a request 675 // for subsequent records when working with resumption tokens will fail 676 Element paramEl = req.getOwnerDocument().createElement(OAIXML.PARAM); 677 paramEl.setAttribute(OAIXML.NAME, OAIXML.METADATA_PREFIX); 678 paramEl.setAttribute(OAIXML.VALUE, meta_prefix); 679 req.appendChild(paramEl); 680 } 681 } else { // no metadata_prefix 682 683 // it must have a metadataPrefix 684 /** Here I disagree with the OAI specification: even if a resumptionToken is 685 * included in the request, the metadataPrefix is a must argument. Otherwise 686 * how would we know what metadataPrefix the harvester requested in his last request? 687 */ 688 logger.error("no metadataPrefix"); 689 return getMessage(OAIXML.createErrorElement(OAIXML.BAD_ARGUMENT, "")); 690 } 691 } 692 693 //Now that we got a prefix, check and see if it's supported by this repository 694 String prefix_value = param_map.get(OAIXML.METADATA_PREFIX); 695 if (containsMetadataPrefix(prefix_value) == false) { 696 logger.error("requested prefix is not found in OAIConfig.xml"); 697 return getMessage(OAIXML.createErrorElement(OAIXML.CANNOT_DISSEMINATE_FORMAT, "")); 698 } 699 700 701 //Now that all validation has been done, I hope, we can send request to the message router 702 Element result = null; 703 String verb = req.getAttribute(OAIXML.TO); 704 NodeList param_list = req.getElementsByTagName(OAIXML.PARAM); 705 ArrayList<Element> retain_param_list = new ArrayList<Element>(); 706 for (int j=0; j<param_list.getLength(); j++) { 707 Element e = OAIXML.duplicateElement(msg.getOwnerDocument(), (Element)param_list.item(j), true); 708 retain_param_list.add(e); 709 } 710 711 //re-organize the request element 712 // reset the 'to' attribute 713 if (request_set == false) { 714 //coll_name could be "", which means it's requesting all records of all collections 715 //we send a request to each collection asking for its records 716 for(int i=0; i<oai_coll_size; i++) { 717 if(req == null) { 718 req = msg.getOwnerDocument().createElement(GSXML.REQUEST_ELEM); 719 msg.appendChild(req); 720 for (int j=0; j<retain_param_list.size(); j++) { 721 req.appendChild(retain_param_list.get(j)); 722 } 723 } 724 String full_name = ((Element)collection_list.item(i)).getAttribute(OAIXML.NAME); 725 coll_name = full_name.substring(full_name.indexOf(":") + 1); 726 req.setAttribute(OAIXML.TO, coll_name + "/" + verb); 727 //logger.info(GSXML.xmlNodeToString(req)); 728 Node n = mr.process(msg); 729 Element e = converter.nodeToElement(n); 730 result = collectAll(result, e, verb, OAIXML.RECORD); 731 732 //clear the content of the old request element 733 msg.removeChild(req); 734 req = null; 735 } 736 } else { 737 req.setAttribute(OAIXML.TO, coll_name + "/" + verb); 738 739 Node result_node = mr.process(msg); 740 result = converter.nodeToElement(result_node); 741 } 742 743 if (result == null) { 744 logger.info("message router returns null"); 745 return getMessage(OAIXML.createErrorElement("Internal service returns null", "")); 746 } 747 Element res = (Element)GSXML.getChildByTagName(result, OAIXML.RESPONSE); 748 if(res == null) { 749 logger.info("response element in xml_result is null"); 750 return getMessage(OAIXML.createErrorElement("Internal service returns null", "")); 751 } 752 NodeList record_list = res.getElementsByTagName(OAIXML.RECORD); 753 int num_records = record_list.getLength(); 754 if(num_records == 0) { 755 logger.info("message router returns 0 records."); 756 return getMessage(OAIXML.createErrorElement(OAIXML.NO_RECORDS_MATCH, "")); 757 } 758 759 //The request coming in does not contain a token, but we have to check the resume_after value and see if we need to issue a resumption token and 760 // save the token as well. 761 if (token.equals("") == true) { 762 if(resume_after < 0 || num_records <= resume_after) { 763 //send the whole list of records 764 return result; 765 } 766 767 //append required number of records (may be a complete or incomplete list) 768 getRecords(list_records, record_list, 0, resume_after); 769 //An incomplete list is sent; append a resumptionToken element 770 Element token_elem = createResumptionTokenElement(num_records, 0, resume_after, true, param_map.get(OAIXML.METADATA_PREFIX)); 771 //store this token 772 OAIXML.addToken(token_elem); 773 774 list_records.appendChild(token_elem); 775 return getMessage(list_records); 776 } 777 778 if (token.equals("") == false) { 779 //get an appropriate number of records (partial list) according to the token 780 //take out the cursor value, which is the size of previously sent list 781 int index = token.indexOf(":"); 782 int cursor = Integer.parseInt(token.substring(index + 1)); 783 Element token_elem = null; 784 785 // are we sending the final part of a complete list? 786 if(cursor + resume_after >= num_records) { 787 //Yes, we are. 788 //append required records to list_records (list is complete) 789 getRecords(list_records, record_list, cursor, num_records); 790 //An incomplete list is sent; append a resumptionToken element 791 token_elem = createResumptionTokenElement(num_records, cursor, -1, false, param_map.get(OAIXML.METADATA_PREFIX)); 792 list_records.appendChild(token_elem); 793 641 } 642 643 // result token XML 644 long expiration_date = -1; 645 if (empty_result_token) { 646 // we know how many records in total as we have sent them all 647 total_size = cursor+num_collected_records; 794 648 } else { 795 //No, we are not. 796 //append required records to list_records (list is incomplete) 797 getRecords(list_records, record_list, cursor, cursor + resume_after); 798 token_elem = createResumptionTokenElement(num_records, cursor, cursor + resume_after, true, param_map.get(OAIXML.METADATA_PREFIX)); 799 //store this token 800 OAIXML.addToken(token_elem); 801 list_records.appendChild(token_elem); 802 } 803 804 return getMessage(list_records); 805 }//end of if(!token.equals("")) 806 807 return result;//a backup return 808 } 649 // non-empty token, set the expiration date 650 expiration_date = OAIResumptionToken.getExpirationDate(token); 651 } 652 Element token_elem = OAIXML.createResumptionTokenElement(result_doc, token, total_size, cursor, expiration_date); 653 // OAIXML.addToken(token_elem); // store it 654 result_element.appendChild(token_elem); // add to the result 655 } 656 657 658 return getMessage(result_doc, result_element); 659 } 660 661 private void addRecordsToList(Document doc, Element result_element, NodeList 662 record_list, int start_point, int num_records) { 663 int end_point = start_point + num_records; 664 for (int i=start_point; i<end_point; i++) { 665 result_element.appendChild(doc.importNode(record_list.item(i), true)); 666 } 667 } 668 669 809 670 // method exclusively used by doListRecords/doListIdentifiers 810 671 private void getRecords(Element verb_elem, NodeList list, int start_point, int end_point) { … … 818 679 return msg; 819 680 } 820 Element res_in_result = (Element)GSXML.getChildByTagName(result, OAIXML.RESPONSE);681 Element res_in_result = (Element)GSXML.getChildByTagName(result, GSXML.RESPONSE_ELEM); 821 682 if(res_in_result == null) { // return the results of all other collections accumulated so far 822 683 return msg; 823 684 } 824 685 Element verb_elem = (Element)GSXML.getChildByTagName(res_in_result, verb); … … 836 697 return result; 837 698 } 838 /** there are three possible exception conditions: bad argument, idDoesNotExist, and noMetadataFormats. 839 * The first one is handled here, and the last two are processed by OAIPMH. 840 */ 841 private Element doListMetadataFormats(Element msg) { 699 700 701 /** there are three possible exception conditions: bad argument, idDoesNotExist, and noMetadataFormat. 702 * The first one is handled here, and the last two are processed by OAIPMH. 703 */ 704 private Element doListMetadataFormats(Element req) { 842 705 //if the verb is ListMetadataFormats, there could be only one parameter: identifier 843 706 //, or there is no parameter; otherwise it is an error 844 707 //logger.info("" + this.converter.getString(msg)); 845 708 846 Element list_metadata_formats = OAIXML.createElement(OAIXML.LIST_METADATA_FORMATS); 847 848 Element req = (Element)GSXML.getChildByTagName(msg, GSXML.REQUEST_ELEM); 849 if (req == null) { logger.error(""); return null; } 850 NodeList params = GSXML.getChildrenByTagName(req, OAIXML.PARAM); 709 NodeList params = GSXML.getChildrenByTagName(req, GSXML.PARAM_ELEM); 851 710 Element param = null; 711 Document lmf_doc = this.converter.newDOM(); 852 712 if(params.getLength() == 0) { 853 713 //this is requesting metadata formats for the whole repository 854 714 //read the oaiConfig.xml file, return the metadata formats specified there. 715 if (this.listmetadataformats_response != null) { 716 // we have already created it 717 return this.listmetadataformats_response; 718 } 719 720 Element list_metadata_formats = lmf_doc.createElement(OAIXML.LIST_METADATA_FORMATS); 721 855 722 Element format_list = (Element)GSXML.getChildByTagName(oai_config, OAIXML.LIST_METADATA_FORMATS); 856 723 if(format_list == null) { 857 724 logger.error("OAIConfig.xml must contain the supported metadata formats"); 858 return getMessage(list_metadata_formats); 725 // TODO this is internal error, what to do??? 726 return getMessage(lmf_doc, list_metadata_formats); 859 727 } 860 728 NodeList formats = format_list.getElementsByTagName(OAIXML.METADATA_FORMAT); 861 729 for(int i=0; i<formats.getLength(); i++) { 862 Element meta_fmt = OAIXML.createElement(OAIXML.METADATA_FORMAT);730 Element meta_fmt = lmf_doc.createElement(OAIXML.METADATA_FORMAT); 863 731 Element first_meta_format = (Element)formats.item(i); 864 732 //the element also contains mappings, but we don't want them 865 meta_fmt.appendChild( meta_fmt.getOwnerDocument().importNode(GSXML.getChildByTagName(first_meta_format, OAIXML.METADATA_PREFIX), true));866 meta_fmt.appendChild( meta_fmt.getOwnerDocument().importNode(GSXML.getChildByTagName(first_meta_format, OAIXML.SCHEMA), true));867 meta_fmt.appendChild( meta_fmt.getOwnerDocument().importNode(GSXML.getChildByTagName(first_meta_format, OAIXML.METADATA_NAMESPACE), true));733 meta_fmt.appendChild(lmf_doc.importNode(GSXML.getChildByTagName(first_meta_format, OAIXML.METADATA_PREFIX), true)); 734 meta_fmt.appendChild(lmf_doc.importNode(GSXML.getChildByTagName(first_meta_format, OAIXML.SCHEMA), true)); 735 meta_fmt.appendChild(lmf_doc.importNode(GSXML.getChildByTagName(first_meta_format, OAIXML.METADATA_NAMESPACE), true)); 868 736 list_metadata_formats.appendChild(meta_fmt); 869 870 return getMessage(l ist_metadata_formats);737 } 738 return getMessage(lmf_doc, list_metadata_formats); 871 739 872 740 … … 875 743 if (params.getLength() > 1) { 876 744 //Bad argument. Can't be more than one parameters for ListMetadataFormats verb 877 return getMessage(OAIXML.createErrorElement(OAIXML.BAD_ARGUMENT, ""));745 return OAIXML.createErrorMessage(OAIXML.BAD_ARGUMENT, ""); 878 746 } 879 747 880 748 // This is a request for the metadata of a particular item with an identifier 881 /**the request xml is in the form: <request> 882 * <param name=.../> 883 * </request> 884 *And there is a param element and one element only. (No paramList element in between). 885 */ 886 param = (Element)params.item(0); 887 String param_name = param.getAttribute(OAIXML.NAME); 888 String identifier = ""; 889 if (!param_name.equals(OAIXML.IDENTIFIER)) { 890 //Bad argument 891 return getMessage(OAIXML.createErrorElement(OAIXML.BAD_ARGUMENT, "")); 892 } else { 893 identifier = param.getAttribute(OAIXML.VALUE); 894 // the identifier is in the form: <site_name>:<coll_name>:<OID> 895 // so it must contain at least two ':' characters 896 String[] strs = identifier.split(":"); 897 if(strs == null || strs.length < 3) { 898 // the OID may also contain ':' 899 logger.error("identifier is not in the form site:coll:id" + identifier); 900 return getMessage(OAIXML.createErrorElement(OAIXML.ID_DOES_NOT_EXIST, "")); 901 } 749 /**the request xml is in the form: <request> 750 * <param name=.../> 751 * </request> 752 *And there is a param element and one element only. (No paramList element in between). 753 */ 754 param = (Element)params.item(0); 755 String param_name = param.getAttribute(GSXML.NAME_ATT); 756 String identifier = ""; 757 if (!param_name.equals(OAIXML.IDENTIFIER)) { 758 //Bad argument 759 return OAIXML.createErrorMessage(OAIXML.BAD_ARGUMENT, ""); 760 } 761 762 identifier = param.getAttribute(GSXML.VALUE_ATT); 763 // the identifier is in the form: <coll_name>:<OID> 764 // so it must contain at least two ':' characters 765 String[] strs = identifier.split(":"); 766 if(strs == null || strs.length < 2) { 767 // the OID may also contain ':' 768 logger.error("identifier is not in the form coll:id" + identifier); 769 return OAIXML.createErrorMessage(OAIXML.ID_DOES_NOT_EXIST, ""); 770 } 902 771 903 // send request to message router 904 // get the names 905 strs = splitNames(identifier); 906 if(strs == null || strs.length < 3) { 907 logger.error("identifier is not in the form site:coll:id" + identifier); 908 return getMessage(OAIXML.createErrorElement(OAIXML.ID_DOES_NOT_EXIST, "")); 909 } 910 String name_of_site = strs[0]; 911 String coll_name = strs[1]; 912 String oid = strs[2]; 913 914 //re-organize the request element 915 // reset the 'to' attribute 916 String verb = req.getAttribute(OAIXML.TO); 917 req.setAttribute(OAIXML.TO, coll_name + "/" + verb); 918 // reset the identifier element 919 param.setAttribute(OAIXML.NAME, OAIXML.OID); 920 param.setAttribute(OAIXML.VALUE, oid); 921 922 //Now send the request to the message router to process 923 Node result_node = mr.process(msg); 924 return converter.nodeToElement(result_node); 925 } 926 927 928 } 929 930 931 private void appendParam(Element req, String name, String value) { 932 Element param = req.getOwnerDocument().createElement(OAIXML.PARAM); 933 param.setAttribute(OAIXML.NAME, name); 934 param.setAttribute(OAIXML.VALUE, value); 935 req.appendChild(param); 936 } 772 // send request to message router 773 // get the names 774 strs = splitNames(identifier); 775 if(strs == null || strs.length < 2) { 776 logger.error("identifier is not in the form coll:id" + identifier); 777 return OAIXML.createErrorMessage(OAIXML.ID_DOES_NOT_EXIST, ""); 778 } 779 //String name_of_site = strs[0]; 780 String coll_name = strs[0]; 781 String oid = strs[1]; 782 783 //re-organize the request element 784 // reset the 'to' attribute 785 String verb = req.getAttribute(GSXML.TO_ATT); 786 req.setAttribute(GSXML.TO_ATT, coll_name + "/" + verb); 787 // reset the identifier element 788 param.setAttribute(GSXML.NAME_ATT, OAIXML.OID); 789 param.setAttribute(GSXML.VALUE_ATT, oid); 790 791 // TODO is this the best way to do this???? should we create a new request??? 792 Element message = req.getOwnerDocument().createElement(GSXML.MESSAGE_ELEM); 793 message.appendChild(req); 794 //Now send the request to the message router to process 795 Node result_node = mr.process(message); 796 return converter.nodeToElement(result_node); 797 } 798 799 800 801 937 802 private void copyNamedElementfromConfig(Element to_elem, String element_name) { 938 803 Element original_element = (Element)GSXML.getChildByTagName(oai_config, element_name); … … 952 817 if (this.identify_response != null) { 953 818 // we have already created it 954 return getMessage(this.identify_response);955 } 956 957 Element identify = OAIXML.createElement(OAIXML.IDENTIFY);819 return this.identify_response; 820 } 821 Document doc = this.converter.newDOM(); 822 Element identify = doc.createElement(OAIXML.IDENTIFY); 958 823 //do the repository name 959 824 copyNamedElementfromConfig(identify, OAIXML.REPOSITORY_NAME); … … 980 845 long earliestDatestamp = getEarliestDateStamp(collection_list); 981 846 String earliestDatestamp_str = OAIXML.getTime(earliestDatestamp); 982 Element earliestDatestamp_elem = OAIXML.createElement(OAIXML.EARLIEST_DATESTAMP);847 Element earliestDatestamp_elem = doc.createElement(OAIXML.EARLIEST_DATESTAMP); 983 848 GSXML.setNodeText(earliestDatestamp_elem, earliestDatestamp_str); 984 849 identify.appendChild(earliestDatestamp_elem); … … 990 855 991 856 // output the oai identifier 992 Element description = OAIXML.createElement(OAIXML.DESCRIPTION);857 Element description = doc.createElement(OAIXML.DESCRIPTION); 993 858 identify.appendChild(description); 994 Element oaiIdentifier = OAIXML.createOAIIdentifierXML(repository_id, "lucene-jdbm-demo", "ec159e"); 859 // TODO, make this a valid id 860 Element oaiIdentifier = OAIXML.createOAIIdentifierXML(doc, repository_id, "lucene-jdbm-demo", "ec159e"); 995 861 description.appendChild(oaiIdentifier); 996 862 … … 998 864 Element info = (Element)GSXML.getChildByTagName(oai_config, OAIXML.OAI_INFO); 999 865 if (info != null) { 1000 NodeList meta = GSXML.getChildrenByTagName(info, OAIXML. INFO_METADATA);866 NodeList meta = GSXML.getChildrenByTagName(info, OAIXML.METADATA); 1001 867 if (meta != null && meta.getLength() > 0) { 1002 Element gsdl = OAIXML.createGSDLElement( );868 Element gsdl = OAIXML.createGSDLElement(doc); 1003 869 description.appendChild(gsdl); 1004 870 for (int m = 0; m<meta.getLength(); m++) { … … 1009 875 } 1010 876 this.identify_response = identify; 1011 return getMessage( identify);877 return getMessage(doc, identify); 1012 878 } 1013 879 //split setSpec (site_name:coll_name) into an array of strings … … 1021 887 return strs; 1022 888 } 1023 /** split the identifier into < site +collection + OID> as an array1024 It has already been checked that the 'identifier' contains at least two':'1025 889 /** split the identifier into <collection + OID> as an array 890 It has already been checked that the 'identifier' contains at least one ':' 891 */ 1026 892 private String[] splitNames(String identifier) { 1027 893 logger.info(identifier); 1028 String [] strs = new String[ 3];894 String [] strs = new String[2]; 1029 895 int first_colon = identifier.indexOf(":"); 1030 896 if(first_colon == -1) { 1031 897 return null; 1032 898 } 1033 899 strs[0] = identifier.substring(0, first_colon); 1034 1035 String sr = identifier.substring(first_colon + 1); 1036 int second_colon = sr.indexOf(":"); 1037 //logger.error(first_colon + " " + second_colon); 1038 strs[1] = sr.substring(0, second_colon); 1039 1040 strs[2] = sr.substring(second_colon + 1); 900 strs[1] = identifier.substring(first_colon + 1); 1041 901 return strs; 1042 902 } … … 1044 904 * by checking it in the OAIConfig.xml 1045 905 */ 1046 private boolean containsMetadataPrefix(String prefix_value) {906 private boolean repositorySupportsMetadataPrefix(String prefix_value) { 1047 907 NodeList prefix_list = oai_config.getElementsByTagName(OAIXML.METADATA_PREFIX); 1048 908 … … 1054 914 return false; 1055 915 } 1056 private Element doGetRecord(Element msg){916 private Element doGetRecord(Element req){ 1057 917 logger.info(""); 1058 918 /** arguments: 1059 919 identifier: required 1060 920 metadataPrefix: required 1061 * Exceptions: badArgument; cannotDisseminateFormat; idDoesNotExist 1062 */ 1063 Element get_record = OAIXML.createElement(OAIXML.GET_RECORD); 921 * Exceptions: badArgument; cannotDisseminateFormat; idDoesNotExist 922 */ 923 Document doc = this.converter.newDOM(); 924 Element get_record = doc.createElement(OAIXML.GET_RECORD); 1064 925 1065 926 HashSet<String> valid_strs = new HashSet<String>(); … … 1067 928 valid_strs.add(OAIXML.METADATA_PREFIX); 1068 929 1069 Element req = (Element)GSXML.getChildByTagName(msg, GSXML.REQUEST_ELEM); 1070 NodeList params = GSXML.getChildrenByTagName(req, OAIXML.PARAM); 1071 HashMap<String, String> param_map = OAIXML.getParamMap(params); 1072 1073 if(!isValidParam(param_map, valid_strs) || 1074 params.getLength() == 0 || 1075 param_map.containsKey(OAIXML.IDENTIFIER) == false || 1076 param_map.containsKey(OAIXML.METADATA_PREFIX) == false ) { 930 NodeList params = GSXML.getChildrenByTagName(req, GSXML.PARAM_ELEM); 931 HashMap<String, String> param_map = GSXML.getParamMap(params); 932 933 if(!areAllParamsValid(param_map, valid_strs) || 934 params.getLength() == 0 || 935 param_map.containsKey(OAIXML.IDENTIFIER) == false || 936 param_map.containsKey(OAIXML.METADATA_PREFIX) == false ) { 1077 937 logger.error("must have the metadataPrefix/identifier parameter."); 1078 return getMessage(OAIXML.createErrorElement(OAIXML.BAD_ARGUMENT, ""));938 return OAIXML.createErrorMessage(OAIXML.BAD_ARGUMENT, ""); 1079 939 } 1080 940 … … 1083 943 1084 944 // verify the metadata prefix 1085 if ( containsMetadataPrefix(prefix) == false) {945 if (repositorySupportsMetadataPrefix(prefix) == false) { 1086 946 logger.error("requested prefix is not found in OAIConfig.xml"); 1087 return getMessage(OAIXML.createErrorElement(OAIXML.CANNOT_DISSEMINATE_FORMAT, ""));947 return OAIXML.createErrorMessage(OAIXML.CANNOT_DISSEMINATE_FORMAT, ""); 1088 948 } 1089 949 1090 950 // get the names 1091 951 String[] strs = splitNames(identifier); 1092 if(strs == null || strs.length < 3) {1093 logger.error("identifier is not in the form site:coll:id" + identifier);1094 return getMessage(OAIXML.createErrorElement(OAIXML.ID_DOES_NOT_EXIST, ""));952 if(strs == null || strs.length < 2) { 953 logger.error("identifier is not in the form coll:id" + identifier); 954 return OAIXML.createErrorMessage(OAIXML.ID_DOES_NOT_EXIST, ""); 1095 955 } 1096 String name_of_site = strs[0];1097 String coll_name = strs[ 1];1098 String oid = strs[ 2];956 //String name_of_site = strs[0]; 957 String coll_name = strs[0]; 958 String oid = strs[1]; 1099 959 1100 960 //re-organize the request element 1101 961 // reset the 'to' attribute 1102 String verb = req.getAttribute( OAIXML.TO);1103 req.setAttribute( OAIXML.TO, coll_name + "/" + verb);962 String verb = req.getAttribute(GSXML.TO_ATT); 963 req.setAttribute(GSXML.TO_ATT, coll_name + "/" + verb); 1104 964 // reset the identifier element 1105 Element param = GSXML.getNamedElement(req, OAIXML.PARAM, OAIXML.NAME, OAIXML.IDENTIFIER);965 Element param = GSXML.getNamedElement(req, GSXML.PARAM_ELEM, GSXML.NAME_ATT, OAIXML.IDENTIFIER); 1106 966 if (param != null) { 1107 param.setAttribute( OAIXML.NAME, OAIXML.OID);1108 param.setAttribute( OAIXML.VALUE, oid);967 param.setAttribute(GSXML.NAME_ATT, OAIXML.OID); 968 param.setAttribute(GSXML.VALUE_ATT, oid); 1109 969 } 1110 970 1111 971 //Now send the request to the message router to process 972 Element msg = doc.createElement(GSXML.MESSAGE_ELEM); 973 msg.appendChild(doc.importNode(req, true)); 1112 974 Node result_node = mr.process(msg); 1113 975 return converter.nodeToElement(result_node); 1114 976 } 1115 977 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 978 // See OAIConfig.xml 979 // dynamically works out what the earliestDateStamp is, since it varies by collection 980 // returns this time in *milliseconds*. 981 protected long getEarliestDateStamp(NodeList oai_coll) { 982 //do the earliestDatestamp 983 long earliestDatestamp = System.currentTimeMillis(); 984 int oai_coll_size = oai_coll.getLength(); 985 if (oai_coll_size == 0) { 986 logger.info("returned oai collection list is empty. Setting repository earliestDatestamp to be 1970-01-01."); 987 earliestDatestamp = 0; 988 } 989 // the earliestDatestamp is now stored as a metadata element in the collection's buildConfig.xml file 990 // we get the earliestDatestamp among the collections 991 for(int i=0; i<oai_coll_size; i++) { 992 long coll_earliestDatestamp = Long.parseLong(((Element)oai_coll.item(i)).getAttribute(OAIXML.EARLIEST_DATESTAMP)); 993 earliestDatestamp = (earliestDatestamp > coll_earliestDatestamp)? coll_earliestDatestamp : earliestDatestamp; 994 } 995 996 return earliestDatestamp*1000; // converting from seconds to milliseconds 997 } 1136 998 } 999 1000
Note:
See TracChangeset
for help on using the changeset viewer.