1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27 package astrogrid.registry.oai;
28
29 import org.w3c.dom.Text;
30 import ORG.oclc.oai.server.catalog.*;
31
32 import java.io.File;
33
34
35 import java.io.FileInputStream;
36 import java.io.IOException;
37 import java.io.StringReader;
38 import java.io.StringWriter;
39 import java.io.ByteArrayInputStream;
40
41
42
43 import java.util.ArrayList;
44 import java.util.Date;
45 import java.util.Enumeration;
46 import java.util.HashMap;
47 import java.util.Iterator;
48 import java.util.LinkedHashMap;
49 import java.util.LinkedList;
50 import java.util.List;
51 import java.util.Map;
52 import java.util.NoSuchElementException;
53 import java.util.Properties;
54 import java.util.SortedMap;
55 import java.util.StringTokenizer;
56 import java.util.TreeMap;
57 import java.util.Vector;
58
59 import org.w3c.dom.Document;
60 import org.w3c.dom.NodeList;
61 import org.w3c.dom.Node;
62 import org.w3c.dom.Element;
63 import java.net.MalformedURLException;
64
65 import ORG.oclc.oai.server.verb.BadResumptionTokenException;
66 import ORG.oclc.oai.server.verb.CannotDisseminateFormatException;
67 import ORG.oclc.oai.server.verb.OAIInternalServerError;
68 import ORG.oclc.oai.server.verb.IdDoesNotExistException;
69 import ORG.oclc.oai.server.verb.BadArgumentException;
70 import ORG.oclc.oai.server.verb.NoMetadataFormatsException;
71 import ORG.oclc.oai.server.verb.NoSetHierarchyException;
72 import ORG.oclc.oai.server.verb.NoItemsMatchException;
73 import ORG.oclc.oai.server.catalog.helpers.RecordStringHandler;
74
75 import org.xml.sax.SAXException;
76 import javax.xml.parsers.SAXParser;
77 import javax.xml.parsers.SAXParserFactory;
78 import javax.xml.parsers.ParserConfigurationException;
79 import javax.xml.transform.Transformer;
80 import javax.xml.transform.TransformerConfigurationException;
81 import javax.xml.transform.TransformerException;
82 import javax.xml.transform.TransformerFactory;
83 import javax.xml.transform.stream.StreamResult;
84 import javax.xml.transform.stream.StreamSource;
85 import org.astrogrid.xmldb.eXist.server.QueryDBService;
86
87 import org.astrogrid.config.Config;
88 import org.astrogrid.util.DomHelper;
89 import org.astrogrid.registry.server.RegistryServerHelper;
90 import org.astrogrid.registry.server.QueryHelper;
91 import org.astrogrid.registry.common.XSLHelper;
92 import java.util.HashMap;
93 import java.util.Set;
94
95 /***
96 * XMLFileOAICatalog is an implementation of AbstractCatalog interface
97 * with the data sitting in a directory on a filesystem.
98 *
99 * @author Jeff Young, OCLC Online Computer Library Center
100 */
101
102 public class XMLExistOAICatalog extends AbstractCatalog {
103 private static boolean debug=false;
104
105 private SortedMap nativeMap = null;
106 private HashMap resumptionResults=new HashMap();
107 private int maxListSize;
108 private ArrayList sets = null;
109 private Transformer getMetadataTransformer = null;
110 private boolean schemaLocationIndexed = false;
111 private static String returnVersionNumber = null;
112 public static Config conf = null;
113 private Properties props = null;
114
115 static {
116 if(conf == null) {
117 conf = org.astrogrid.config.SimpleConfig.getSingleton();
118 }
119 }
120
121 public XMLExistOAICatalog(Properties properties) throws IOException {
122
123 System.out.println("I am in the constructor");
124 this.props = properties;
125 sets = getSets(properties);
126 }
127
128
129 private void populateNativeMap() throws OAIInternalServerError {
130 try {
131 String versionNumber = props.getProperty("registry_version",null);
132 if(versionNumber == null)
133 versionNumber = conf.getString("org.astrogrid.registry.version");
134
135 versionNumber = versionNumber.replace('.','_');
136 String collectionName = "astrogridv" + versionNumber;
137 String temp;
138 debug = false;
139
140 maxListSize = conf.getInt("XMLFileOAICatalog.maxListSize",8000);
141 if(debug)
142 System.out.println("in XMLFileOAICatalog(): maxListSize=" + maxListSize);
143
144 System.out.println("get the managedauths");
145 HashMap manageAuths = RegistryServerHelper.getManagedAuthorities(collectionName, versionNumber);
146
147 Set keys = manageAuths.keySet();
148 Iterator keyIter = keys.iterator();
149 System.out.println("the keysset size = " + keys.size());
150 if(keys.size() == 0) {
151 throw new OAIInternalServerError("Could not find any authorites managed");
152 }
153 boolean hasAuthorityElement = conf.getBoolean("identifier.path.hasauthorityid." + versionNumber,false);
154 String identWhere = "vr:Identifier/vr:AuthorityID = '";
155 String wildCard = "";
156 if(!hasAuthorityElement) {
157 identWhere = "vr:identifier &= '*";
158 wildCard = "*";
159 }
160
161
162 String auth = (String)keyIter.next();
163 String xqlQuery = QueryHelper.getStartQuery(versionNumber);
164 xqlQuery += identWhere + wildCard + auth + wildCard + "'";
165 while(keyIter.hasNext()) {
166 xqlQuery += " or " + identWhere + wildCard + (String)keyIter.next() + wildCard + "'";
167 }
168 xqlQuery += " return $x";
169 System.out.println("the build xql = " + xqlQuery);
170
171 QueryDBService qs = new QueryDBService();
172 Document sourceFile = qs.runQuery(collectionName,xqlQuery);
173
174 System.out.println("the verisonNumber = " + versionNumber);
175 XSLHelper xsh = new XSLHelper();
176
177
178
179
180
181
182
183
184
185 Document oaiDoc = xsh.transformToOAI(sourceFile,versionNumber);
186
187
188 String xmlDoc = DomHelper.DocumentToString(oaiDoc);
189 ByteArrayInputStream bas = new ByteArrayInputStream(xmlDoc.getBytes());
190 RecordStringHandler rsh = new RecordStringHandler();
191 SAXParserFactory factory = SAXParserFactory.newInstance();
192 factory.setNamespaceAware(true);
193 factory.setFeature("http://xml.org/sax/features/namespace-prefixes", true);
194 SAXParser saxParser = factory.newSAXParser();
195
196 saxParser.parse(bas, rsh);
197 nativeMap = rsh.getNativeRecords();
198
199 } catch (SAXException e) {
200 e.printStackTrace();
201 throw new OAIInternalServerError(e.getMessage());
202 } catch (ParserConfigurationException e) {
203 e.printStackTrace();
204 throw new OAIInternalServerError(e.getMessage());
205 }catch(MalformedURLException e) {
206 e.printStackTrace();
207 throw new OAIInternalServerError(e.getMessage());
208 } catch(IOException ioe) {
209 ioe.printStackTrace();
210 throw new OAIInternalServerError(ioe.getMessage());
211
212 }
213 }
214
215 private static ArrayList getSets(Properties properties) {
216 TreeMap treeMap = new TreeMap();
217 String propertyPrefix = "Sets.";
218 Enumeration propNames = properties.propertyNames();
219 while (propNames.hasMoreElements()) {
220 String propertyName = (String)propNames.nextElement();
221 if (propertyName.startsWith(propertyPrefix)) {
222 treeMap.put(propertyName, properties.get(propertyName));
223 }
224 }
225 return new ArrayList(treeMap.values());
226 }
227
228 /***
229 * Retrieve the specified metadata for the specified oaiIdentifier
230 *
231 * @param oaiIdentifier the OAI identifier
232 * @param metadataPrefix the OAI metadataPrefix
233 * @return the Record object containing the result.
234 * @exception CannotDisseminateFormatException signals an http status
235 * code 400 problem
236 * @exception IdDoesNotExistException signals an http status code 404
237 * problem
238 * @exception OAIInternalServerError signals an http status code 500
239 * problem
240 */
241 public String getRecord(String oaiIdentifier, String metadataPrefix)
242 throws IdDoesNotExistException, CannotDisseminateFormatException,
243 OAIInternalServerError {
244 populateNativeMap();
245 String localIdentifier
246 = ((XMLExistRecordFactory)getRecordFactory()).fromOAIIdentifier(oaiIdentifier);
247 String recordid = localIdentifier;
248 if (schemaLocationIndexed) {
249 recordid=recordid + "/" + metadataPrefix;
250 }
251 if (debug) System.out.println("XMLFileOAICatalog.getRecord: recordid=" + recordid);
252 Object nativeRecord = nativeMap.get(recordid.toLowerCase());
253 if (nativeRecord == null)
254 throw new IdDoesNotExistException(oaiIdentifier);
255 return constructRecord(nativeRecord, metadataPrefix);
256 }
257
258
259 /***
260 * get a DocumentFragment containing the specified record
261 */
262 public String getMetadata(String oaiIdentifier, String metadataPrefix)
263 throws IdDoesNotExistException, IdDoesNotExistException, CannotDisseminateFormatException,
264 OAIInternalServerError {
265 populateNativeMap();
266 String localIdentifier
267 = ((XMLExistRecordFactory)getRecordFactory()).fromOAIIdentifier(oaiIdentifier);
268 String recordid = localIdentifier;
269 if (schemaLocationIndexed) {
270 recordid=recordid + "/" + metadataPrefix;
271 }
272 if (debug) System.out.println("XMLFileOAICatalog.getRecord: recordid=" + recordid);
273 HashMap nativeRecord = (HashMap)nativeMap.get(recordid.toLowerCase());
274 if (nativeRecord == null)
275 throw new IdDoesNotExistException(oaiIdentifier);
276 if (debug) {
277 Iterator keys = nativeRecord.keySet().iterator();
278 while (keys.hasNext())
279 System.out.println(keys.next());
280 }
281 String result = (String)nativeRecord.get("recordString");
282 if (getMetadataTransformer != null) {
283 StringReader stringReader = new StringReader(result);
284 StreamSource streamSource = new StreamSource(stringReader);
285 StringWriter stringWriter = new StringWriter();
286 try {
287 synchronized (getMetadataTransformer) {
288 getMetadataTransformer.transform(streamSource, new StreamResult(stringWriter));
289 }
290 } catch (TransformerException e) {
291 e.printStackTrace();
292 throw new OAIInternalServerError(e.getMessage());
293 }
294 result = stringWriter.toString();
295 }
296 return result;
297 }
298
299 /***
300 * Retrieve a list of schemaLocation values associated with the specified
301 * oaiIdentifier.
302 *
303 * We get passed the ID for a record and are supposed to return a list
304 * of the formats that we can deliver the record in. Since we are assuming
305 * that all the records in the directory have the same format, the
306 * response to this is static;
307 *
308 * @param oaiIdentifier the OAI identifier
309 * @return a Vector containing schemaLocation Strings
310 * @exception OAIBadRequestException signals an http status code 400
311 * problem
312 * @exception OAINotFoundException signals an http status code 404 problem
313 * @exception OAIInternalServerError signals an http status code 500
314 * problem
315 */
316 public Vector getSchemaLocations(String oaiIdentifier)
317 throws IdDoesNotExistException, OAIInternalServerError, NoMetadataFormatsException {
318 Vector v = new Vector();
319 String localIdentifier
320 = ((XMLExistRecordFactory)getRecordFactory()).fromOAIIdentifier(oaiIdentifier);
321 Iterator iterator = nativeMap.entrySet().iterator();
322 int numRows = nativeMap.entrySet().size();
323 for (int i=0; i<numRows; ++i) {
324 Map.Entry entryNativeMap = (Map.Entry)iterator.next();
325 HashMap nativeRecord = (HashMap)entryNativeMap.getValue();
326 if (((XMLExistRecordFactory)getRecordFactory()).getOAIIdentifier(nativeRecord).equals(oaiIdentifier)) {
327 Vector schemaLocations = ((XMLExistRecordFactory)getRecordFactory()).getSchemaLocations(nativeRecord);
328 Iterator itemIterator = schemaLocations.iterator();
329 while (itemIterator.hasNext())
330 v.add(itemIterator.next());
331 }
332 }
333 if (v.size() > 0) {
334 return v;
335 } else {
336 throw new IdDoesNotExistException(oaiIdentifier);
337 }
338 }
339
340
341 /***
342 * Retrieve a list of Identifiers that satisfy the criteria parameters
343 *
344 * @param from beginning date in the form of YYYY-MM-DD or null if earliest
345 * date is desired
346 * @param until ending date in the form of YYYY-MM-DD or null if latest
347 * date is desired
348 * @param set set name or null if no set is desired
349 * @return a Map object containing an optional "resumptionToken" key/value
350 * pair and an "identifiers" Map object. The "identifiers" Map contains OAI
351 * identifier keys with corresponding values of "true" or null depending on
352 * whether the identifier is deleted or not.
353 * @exception OAIBadRequestException signals an http status code 400
354 * problem
355 * @exception OAIInternalServerError signals an http status code 500
356 * problem
357 */
358 public Map listIdentifiers(String from, String until, String set, String metadataPrefix)
359 throws BadArgumentException, CannotDisseminateFormatException, OAIInternalServerError,
360 NoItemsMatchException {
361 populateNativeMap();
362 purge();
363 Map listIdentifiersMap = new HashMap();
364 ArrayList headers = new ArrayList();
365 ArrayList identifiers = new ArrayList();
366 Iterator iterator = nativeMap.entrySet().iterator();
367 int numRows = nativeMap.entrySet().size();
368 int count = 0;
369 while (count < maxListSize && iterator.hasNext()) {
370 Map.Entry entryNativeMap = (Map.Entry)iterator.next();
371 HashMap nativeRecord = (HashMap)entryNativeMap.getValue();
372 String recordDate = ((XMLExistRecordFactory)getRecordFactory()).getDatestamp(nativeRecord);
373 String schemaLocation = (String)nativeRecord.get("schemaLocation");
374 List setSpecs = (List)nativeRecord.get("setSpecs");
375 if (recordDate.compareTo(from) >= 0
376 && recordDate.compareTo(until) <= 0
377 && (!schemaLocationIndexed || schemaLocation.equals(getCrosswalks().getSchemaLocation(metadataPrefix)))
378 && (set==null || setSpecs.contains(set))) {
379 String[] header = ((XMLExistRecordFactory)getRecordFactory()).createHeader(nativeRecord);
380 headers.add(header[0]);
381 identifiers.add(header[1]);
382 count++;
383 }
384 }
385
386 if (count == 0)
387 throw new NoItemsMatchException();
388
389
390 if (iterator.hasNext()) {
391 String resumptionId = getRSName();
392 resumptionResults.put(resumptionId, iterator);
393
394 /******************************************************************
395 * Construct the resumptionToken String however you see fit.
396 *****************************************************************/
397 StringBuffer resumptionTokenSb = new StringBuffer();
398 resumptionTokenSb.append(resumptionId);
399 resumptionTokenSb.append(":");
400 resumptionTokenSb.append(Integer.toString(count));
401 resumptionTokenSb.append(":");
402 resumptionTokenSb.append(Integer.toString(numRows));
403 resumptionTokenSb.append(":");
404 resumptionTokenSb.append(metadataPrefix);
405
406 /******************************************************************
407 * Use the following line if you wish to include the optional
408 * resumptionToken attributes in the response. Otherwise, use the
409 * line after it that I've commented out.
410 *****************************************************************/
411 listIdentifiersMap.put("resumptionMap",
412 getResumptionMap(resumptionTokenSb.toString(),
413 numRows,
414 0));
415
416
417 }
418 listIdentifiersMap.put("headers", headers.iterator());
419 listIdentifiersMap.put("identifiers", identifiers.iterator());
420 return listIdentifiersMap;
421 }
422
423 /***
424 * Retrieve the next set of Identifiers associated with the resumptionToken
425 *
426 * @param resumptionToken implementation-dependent format taken from the
427 * previous listIdentifiers() Map result.
428 * @return a Map object containing an optional "resumptionToken" key/value
429 * pair and an "identifiers" Map object. The "identifiers" Map contains OAI
430 * identifier keys with corresponding values of "true" or null depending on
431 * whether the identifier is deleted or not.
432 * @exception OAIBadRequestException signals an http status code 400
433 * problem
434 * @exception OAIInternalServerError signals an http status code 500
435 * problem
436 */
437 public Map listIdentifiers(String resumptionToken)
438 throws BadResumptionTokenException, OAIInternalServerError {
439 purge();
440 Map listIdentifiersMap = new HashMap();
441 ArrayList headers = new ArrayList();
442 ArrayList identifiers = new ArrayList();
443
444 /***********************************************************************
445 * parse your resumptionToken and look it up in the resumptionResults,
446 * if necessary
447 **********************************************************************/
448 StringTokenizer tokenizer = new StringTokenizer(resumptionToken, ":");
449 String resumptionId;
450 int oldCount;
451 String metadataPrefix;
452 int numRows;
453 try {
454 resumptionId = tokenizer.nextToken();
455 oldCount = Integer.parseInt(tokenizer.nextToken());
456 numRows = Integer.parseInt(tokenizer.nextToken());
457 metadataPrefix = tokenizer.nextToken();
458 } catch (NoSuchElementException e) {
459 throw new BadResumptionTokenException();
460 }
461
462
463 Iterator iterator = (Iterator)resumptionResults.remove(resumptionId);
464 if (iterator == null) {
465 System.out.println("XMLFileOAICatalog.listIdentifiers: reuse of old resumptionToken?");
466 iterator = nativeMap.entrySet().iterator();
467 for (int i = 0; i<oldCount; ++i)
468 iterator.next();
469 }
470
471
472 int count = 0;
473 while (count < maxListSize && iterator.hasNext()) {
474 Map.Entry entryNativeMap = (Map.Entry)iterator.next();
475 Object nativeRecord = nativeMap.get((String)entryNativeMap.getKey());
476 String[] header = ((XMLExistRecordFactory)getRecordFactory()).createHeader(nativeRecord);
477 headers.add(header[0]);
478 identifiers.add(header[1]);
479 count++;
480 }
481
482
483 if (iterator.hasNext()) {
484 resumptionId = getRSName();
485 resumptionResults.put(resumptionId, iterator);
486
487 /******************************************************************
488 * Construct the resumptionToken String however you see fit.
489 *****************************************************************/
490 StringBuffer resumptionTokenSb = new StringBuffer();
491 resumptionTokenSb.append(resumptionId);
492 resumptionTokenSb.append(":");
493 resumptionTokenSb.append(Integer.toString(oldCount + count));
494 resumptionTokenSb.append(":");
495 resumptionTokenSb.append(Integer.toString(numRows));
496 resumptionTokenSb.append(":");
497 resumptionTokenSb.append(metadataPrefix);
498
499 /******************************************************************
500 * Use the following line if you wish to include the optional
501 * resumptionToken attributes in the response. Otherwise, use the
502 * line after it that I've commented out.
503 *****************************************************************/
504 listIdentifiersMap.put("resumptionMap", getResumptionMap(resumptionTokenSb.toString(),
505 numRows,
506 oldCount));
507
508
509 }
510
511 listIdentifiersMap.put("headers", headers.iterator());
512 listIdentifiersMap.put("identifiers", identifiers.iterator());
513 return listIdentifiersMap;
514 }
515
516
517 /***
518 * Utility method to construct a Record object for a specified
519 * metadataFormat from a native record
520 *
521 * @param nativeRecord native item from the dataase
522 * @param metadataPrefix the desired metadataPrefix for performing the crosswalk
523 * @return the <record/> String
524 * @exception CannotDisseminateFormatException the record is not available
525 * for the specified metadataPrefix.
526 */
527 private String constructRecord(Object nativeRecord, String metadataPrefix)
528 throws CannotDisseminateFormatException, OAIInternalServerError {
529 String schemaURL = null;
530 Iterator setSpecs = getSetSpecs(nativeRecord);
531 Iterator abouts = getAbouts(nativeRecord);
532
533 if (metadataPrefix != null) {
534 if (debug) {
535 System.out.println(getCrosswalks());
536 }
537 if ((schemaURL = getCrosswalks().getSchemaURL(metadataPrefix)) == null)
538 throw new CannotDisseminateFormatException(metadataPrefix);
539 }
540 System.out.println("calling create for metataDataPrefix = " + metadataPrefix);
541 return ((XMLExistRecordFactory)getRecordFactory()).create(nativeRecord, schemaURL, metadataPrefix, setSpecs, abouts);
542 }
543
544 /***
545 * get an Iterator containing the setSpecs for the nativeRecord
546 *
547 * @param rs ResultSet containing the nativeRecord
548 * @return an Iterator containing the list of setSpec values for this nativeRecord
549 */
550 private Iterator getSetSpecs(Object nativeRecord)
551 throws OAIInternalServerError {
552 try {
553 return ((XMLExistRecordFactory)getRecordFactory()).getSetSpecs(nativeRecord);
554 } catch (Exception e) {
555 e.printStackTrace();
556 throw new OAIInternalServerError(e.getMessage());
557 }
558 }
559
560 /***
561 * get an Iterator containing the abouts for the nativeRecord
562 *
563 * @param rs ResultSet containing the nativeRecord
564 * @return an Iterator containing the list of about values for this nativeRecord
565 */
566 private Iterator getAbouts(Object nativeRecord)
567 throws OAIInternalServerError {
568 return null;
569 }
570
571 /***
572 * Retrieve a list of records that satisfy the specified criteria
573 *
574 * @param from beginning date in the form of YYYY-MM-DD or null if earliest
575 * date is desired
576 * @param until ending date in the form of YYYY-MM-DD or null if latest
577 * date is desired
578 * @param set set name or null if no set is desired
579 * @param metadataPrefix the OAI metadataPrefix
580 * @return a Map object containing an optional "resumptionToken" key/value
581 * pair and a "records" Iterator object. The "records" Iterator contains a
582 * set of Records objects.
583 * @exception OAIBadRequestException signals an http status code 400
584 * problem
585 * @exception OAIInternalServerError signals an http status code 500
586 * problem
587 */
588 public Map listRecords(String from, String until, String set,
589 String metadataPrefix)
590 throws BadArgumentException, CannotDisseminateFormatException,
591 OAIInternalServerError, NoItemsMatchException {
592 populateNativeMap();
593 String requestedSchemaLocation = getCrosswalks().getSchemaLocation(metadataPrefix);
594 purge();
595 Map listRecordsMap = new HashMap();
596 LinkedList records = new LinkedList();
597 Iterator iterator = nativeMap.entrySet().iterator();
598 int numRows = nativeMap.entrySet().size();
599 if (debug) {
600 System.out.println("XMLFileOAICatalog.listRecords: numRows=" + numRows);
601 }
602 int count = 0;
603 while (count < maxListSize && iterator.hasNext()) {
604 Map.Entry entryNativeMap = (Map.Entry)iterator.next();
605 HashMap nativeRecord = (HashMap)entryNativeMap.getValue();
606 String recordDate = ((XMLExistRecordFactory)getRecordFactory()).getDatestamp(nativeRecord);
607 String schemaLocation = (String)nativeRecord.get("schemaLocation");
608 List setSpecs = (List)nativeRecord.get("setSpecs");
609 if (debug) {
610 System.out.println("XMLFileOAICatalog.listRecord: recordDate=" + recordDate);
611 System.out.println("XMLFileOAICatalog.listRecord: requestedSchemaLocation=" + requestedSchemaLocation);
612 System.out.println("XMLFileOAICatalog.listRecord: schemaLocation=" + schemaLocation);
613 }
614 if (recordDate.compareTo(from) >= 0
615 && recordDate.compareTo(until) <= 0
616 && (!schemaLocationIndexed || requestedSchemaLocation.equals(schemaLocation))
617 && (set==null || setSpecs.contains(set))) {
618 String record = constructRecord(nativeRecord, metadataPrefix);
619 if (debug) {
620 System.out.println("XMLFileOAICatalog.listRecords: record=" + record);
621 }
622 records.add(record);
623 count++;
624 }
625 }
626
627 if (count == 0)
628 throw new NoItemsMatchException();
629
630
631 if (iterator.hasNext()) {
632 String resumptionId = getRSName();
633 resumptionResults.put(resumptionId, iterator);
634
635 /******************************************************************
636 * Construct the resumptionToken String however you see fit.
637 *****************************************************************/
638 StringBuffer resumptionTokenSb = new StringBuffer();
639 resumptionTokenSb.append(resumptionId);
640 resumptionTokenSb.append(":");
641 resumptionTokenSb.append(Integer.toString(count));
642 resumptionTokenSb.append(":");
643 resumptionTokenSb.append(Integer.toString(numRows));
644 resumptionTokenSb.append(":");
645 resumptionTokenSb.append(metadataPrefix);
646
647 /******************************************************************
648 * Use the following line if you wish to include the optional
649 * resumptionToken attributes in the response. Otherwise, use the
650 * line after it that I've commented out.
651 *****************************************************************/
652 listRecordsMap.put("resumptionMap",
653 getResumptionMap(resumptionTokenSb.toString(),
654 numRows,
655 0));
656
657
658 }
659 listRecordsMap.put("records", records.iterator());
660 return listRecordsMap;
661 }
662
663
664 /***
665 * Retrieve the next set of records associated with the resumptionToken
666 *
667 * @param resumptionToken implementation-dependent format taken from the
668 * previous listRecords() Map result.
669 * @return a Map object containing an optional "resumptionToken" key/value
670 * pair and a "records" Iterator object. The "records" Iterator contains a
671 * set of Records objects.
672 * @exception OAIBadRequestException signals an http status code 400
673 * problem
674 * @exception OAIInternalServerError signals an http status code 500
675 * problem
676 */
677 public Map listRecords(String resumptionToken)
678 throws BadResumptionTokenException,
679 OAIInternalServerError {
680 purge();
681 Map listRecordsMap = new HashMap();
682 LinkedList records = new LinkedList();
683
684 /***********************************************************************
685 * parse your resumptionToken and look it up in the resumptionResults,
686 * if necessary
687 **********************************************************************/
688 StringTokenizer tokenizer = new StringTokenizer(resumptionToken, ":");
689 String resumptionId;
690 int oldCount;
691 String metadataPrefix;
692 int numRows;
693 try {
694 resumptionId = tokenizer.nextToken();
695 oldCount = Integer.parseInt(tokenizer.nextToken());
696 numRows = Integer.parseInt(tokenizer.nextToken());
697 metadataPrefix = tokenizer.nextToken();
698 } catch (NoSuchElementException e) {
699 throw new BadResumptionTokenException();
700 }
701
702
703 Iterator iterator = (Iterator)resumptionResults.remove(resumptionId);
704 if (iterator == null) {
705 System.out.println("XMLFileOAICatalog.listRecords: reuse of old resumptionToken?");
706 iterator = nativeMap.entrySet().iterator();
707 for (int i = 0; i<oldCount; ++i)
708 iterator.next();
709 }
710
711
712 int count = 0;
713 while (count < maxListSize && iterator.hasNext()) {
714 Map.Entry entryNativeMap = (Map.Entry)iterator.next();
715 try {
716 Object nativeRecord = nativeMap.get((String)entryNativeMap.getKey());
717 String record = constructRecord(nativeRecord, metadataPrefix);
718 records.add(record);
719 count++;
720 } catch (CannotDisseminateFormatException e) {
721
722 throw new BadResumptionTokenException();
723 }
724 }
725
726
727 if (iterator.hasNext()) {
728 resumptionId = getRSName();
729 resumptionResults.put(resumptionId, iterator);
730
731 /******************************************************************
732 * Construct the resumptionToken String however you see fit.
733 *****************************************************************/
734 StringBuffer resumptionTokenSb = new StringBuffer();
735 resumptionTokenSb.append(resumptionId);
736 resumptionTokenSb.append(":");
737 resumptionTokenSb.append(Integer.toString(oldCount + count));
738 resumptionTokenSb.append(":");
739 resumptionTokenSb.append(Integer.toString(numRows));
740 resumptionTokenSb.append(":");
741 resumptionTokenSb.append(metadataPrefix);
742
743 /******************************************************************
744 * Use the following line if you wish to include the optional
745 * resumptionToken attributes in the response. Otherwise, use the
746 * line after it that I've commented out.
747 *****************************************************************/
748 listRecordsMap.put("resumptionMap", getResumptionMap(resumptionTokenSb.toString(),
749 numRows,
750 oldCount));
751
752
753 }
754
755 listRecordsMap.put("records", records.iterator());
756 return listRecordsMap;
757 }
758
759
760 public Map listSets() throws OAIInternalServerError,
761 NoSetHierarchyException {
762 if (sets.size() == 0)
763 throw new NoSetHierarchyException();
764 Map listSetsMap = new LinkedHashMap();
765 listSetsMap.put("sets", sets.iterator());
766 return listSetsMap;
767 }
768
769
770 public Map listSets(String resumptionToken)
771 throws BadResumptionTokenException, OAIInternalServerError {
772 throw new BadResumptionTokenException();
773 }
774
775
776 /***
777 * close the repository
778 */
779 public void close() { }
780
781
782 /***
783 * Purge tokens that are older than the time-to-live.
784 */
785 private void purge() {
786 ArrayList old = new ArrayList();
787 Date then, now = new Date();
788 Iterator keySet = resumptionResults.keySet().iterator();
789 String key;
790
791 while (keySet.hasNext()) {
792 key=(String)keySet.next();
793 then=new Date(Long.parseLong(key)+getMillisecondsToLive());
794 if (now.after(then)) {
795 old.add(key);
796 }
797 }
798 Iterator iterator = old.iterator();
799 while (iterator.hasNext()) {
800 key = (String)iterator.next();
801 resumptionResults.remove(key);
802 }
803 }
804
805
806 /***
807 * Use the current date as the basis for the resumptiontoken
808 *
809 * @return a long integer version of the current time
810 */
811 private synchronized static String getRSName() {
812 Date now = new Date();
813 return Long.toString(now.getTime());
814 }
815 }