View Javadoc

1   /*
2    * The contents of this file, as updated from time to time by the OCLC Office
3    * of Research, are subject to the OCLC Office of Research Public License
4    * Version 1.0 (the "License"); you may not use this file except in compliance
5    * with the License. You may obtain a current copy of the License at
6    * http://purl.oclc.org/oclc/research/ORPL/.
7    *
8    * Software distributed under the License is distributed on an "AS IS" basis,
9    * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
10   * for the specific language governing rights and limitations under the License.
11   *
12   * This software consists of voluntary contributions made by many individuals
13   * on behalf of the OCLC Office of Research. For more information on the OCLC
14   * Office of Research, please see http://www.oclc.org/oclc/research/.
15   *
16   * This is Original Code.
17   *
18   * The Initial Developer(s) of the Original Code is (are):
19   *  - Ralph LeVan <levan@oclc.org>
20   *
21   * Portions created by OCLC are Copyright (C) 2001.
22   *
23   * 2002-04-09 Created
24   */
25  
26  //package ORG.oclc.oai.server.catalog;
27  package astrogrid.registry.oai;
28  
29  import org.w3c.dom.Text;
30  import ORG.oclc.oai.server.catalog.*;
31  // import java.io.BufferedInputStream;
32  import java.io.File;
33  // import java.io.FileNotFoundException;
34  // import java.io.FileInputStream;
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  // // import java.net.URI;
41  // // import java.net.URISyntaxException;
42  // import java.net.URLEncoder;
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  // import java.lang.reflect.*;
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  // // import ORG.oclc.oai.util.*;
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            //Document resultDoc = null;
174            System.out.println("the verisonNumber = " + versionNumber);
175            XSLHelper xsh = new XSLHelper();
176            //resultDoc = sourceFile;
177            /*
178            if(!versionNumber.equals(returnVersionNumber)) {
179               resultDoc = xsh.transformResultVersions(sourceFile,versionNumber,returnVersionNumber);
180            }else {
181               resultDoc = sourceFile;
182            }
183            */
184            //Document oaiDoc = xsh.transformToOAI(resultDoc,versionNumber);
185            Document oaiDoc = xsh.transformToOAI(sourceFile,versionNumber);
186 
187            //System.out.println("the oai version = " + DomHelper.DocumentToString(oaiDoc));
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            //saxParser.parse(new File(sourceFile), rsh);
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(); // clean out old resumptionTokens
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    /* decide if you're done */
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 //        listIdentifiersMap.put("resumptionMap",
416 //                getResumptionMap(resumptionTokenSb.toString()));
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(); // clean out old resumptionTokens
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    /* Get some more records from your database */
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    /* load the headers and identifiers ArrayLists. */
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    /* decide if you're done. */
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        //          listIdentifiersMap.put("resumptionMap",
508        //                                 getResumptionMap(resumptionTokenSb.toString()));
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(); // clean out old resumptionTokens
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    /* decide if you're done */
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 //        listRecordsMap.put("resumptionMap",
657 //                getResumptionMap(resumptionTokenSb.toString()));
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(); // clean out old resumptionTokens
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    /* Get some more records from your database */
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    /* load the records ArrayLists. */
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                 /* the client hacked the resumptionToken beyond repair */
722                 throw new BadResumptionTokenException();
723        }
724    }
725 
726    /* decide if you're done. */
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        //          listRecordsMap.put("resumptionMap",
752        //                                 getResumptionMap(resumptionTokenSb.toString()));
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 }