Project

General

Profile

1
/**
2
 * Copyright 2006 OCLC Online Computer Library Center Licensed under the Apache
3
 * License, Version 2.0 (the "License"); you may not use this file except in
4
 * compliance with the License. You may obtain a copy of the License at
5
 * http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or
6
 * agreed to in writing, software distributed under the License is distributed on
7
 * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
8
 * or implied. See the License for the specific language governing permissions and
9
 * limitations under the License.
10
 */
11
package edu.ucsb.nceas.metacat.oaipmh.provider.server.catalog;
12

    
13
import java.io.File;
14
import java.io.IOException;
15
import java.io.Reader;
16
import java.sql.Connection;
17
import java.sql.DriverManager;
18
import java.sql.ResultSet;
19
import java.sql.SQLException;
20
import java.sql.SQLWarning;
21
import java.sql.Statement;
22
import java.text.SimpleDateFormat;
23
import java.util.ArrayList;
24
import java.util.Date;
25
import java.util.HashMap;
26
import java.util.Iterator;
27
import java.util.Map;
28
import java.util.NoSuchElementException;
29
import java.util.Properties;
30
import java.util.StringTokenizer;
31
import java.util.Vector;
32

    
33
import org.apache.log4j.Logger;
34

    
35
import edu.ucsb.nceas.metacat.client.DocumentNotFoundException;
36
import edu.ucsb.nceas.metacat.client.InsufficientKarmaException;
37
import edu.ucsb.nceas.metacat.client.Metacat;
38
import edu.ucsb.nceas.metacat.client.MetacatException;
39
import edu.ucsb.nceas.metacat.client.MetacatFactory;
40
import edu.ucsb.nceas.metacat.client.MetacatInaccessibleException;
41
import edu.ucsb.nceas.metacat.oaipmh.provider.server.OAIHandler;
42
import edu.ucsb.nceas.metacat.service.PropertyService;
43
import edu.ucsb.nceas.metacat.service.ServiceException;
44
import edu.ucsb.nceas.metacat.util.SystemUtil;
45
import edu.ucsb.nceas.utilities.IOUtil;
46
import edu.ucsb.nceas.utilities.PropertyNotFoundException;
47

    
48
import ORG.oclc.oai.server.catalog.AbstractCatalog;
49
import ORG.oclc.oai.server.catalog.RecordFactory;
50
import ORG.oclc.oai.server.verb.BadResumptionTokenException;
51
import ORG.oclc.oai.server.verb.CannotDisseminateFormatException;
52
import ORG.oclc.oai.server.verb.IdDoesNotExistException;
53
import ORG.oclc.oai.server.verb.NoItemsMatchException;
54
import ORG.oclc.oai.server.verb.NoMetadataFormatsException;
55
import ORG.oclc.oai.server.verb.NoSetHierarchyException;
56
import ORG.oclc.oai.server.verb.OAIInternalServerError;
57

    
58

    
59
/**
60
 * MetacatCatalog is an implementation of AbstractCatalog interface.
61
 * 
62
 * @author Ralph LeVan, OCLC Online Computer Library Center
63
 */
64

    
65
public class MetacatCatalog extends AbstractCatalog {
66
  
67
  /* Class fields */
68
  
69
  private static final Logger logger = Logger.getLogger(MetacatCatalog.class);
70

    
71
  /** Database connection */
72
  private static String metacatDBDriver;
73
  private static String metacatDBURL;
74
  private static String metacatDBUser;
75
  private static String metacatDBPassword;
76
  private static String metacatURL;
77
  
78

    
79
  /* Instance fields */
80
  
81
  private SimpleDateFormat dateFormatter = new SimpleDateFormat();
82
  protected String homeDir;
83
  private HashMap<String, String> dateMap = new HashMap<String, String>();
84
  private HashMap<String, String> docTypeMap = new HashMap<String, String>();
85
  private HashMap resumptionResults = new HashMap();
86
  private int maxListSize;
87
  
88
  
89
  /* Constructors */
90
  
91
  public MetacatCatalog(Properties properties) {
92
    String errorStr;
93
    String temp;
94

    
95
    dateFormatter.applyPattern("yyyy-MM-dd'T'HH:mm:ss'Z'");
96
    
97
    temp = properties.getProperty("oaipmh.maxListSize");
98
    if (temp == null) {
99
      errorStr = "oaipmh.maxListSize is missing from the properties file";
100
      throw new IllegalArgumentException(errorStr);
101
    }
102
    maxListSize = Integer.parseInt(temp);
103
    
104
    metacatDBDriver = properties.getProperty("database.driver");
105
    metacatDBURL = properties.getProperty("database.connectionURI");
106
    metacatDBUser = properties.getProperty("database.user");
107
    metacatDBPassword = properties.getProperty("database.password");
108
    
109
    try {
110
      if (OAIHandler.isIntegratedWithMetacat()) {
111
        metacatURL = SystemUtil.getServletURL();
112
      }
113
      else {
114
        metacatURL = "http://localhost:8080/knb/metacat";
115
      }
116
      
117
      logger.warn("metacatURL: " + metacatURL);
118
    }
119
    catch (PropertyNotFoundException e) {
120
      logger.error("PropertyNotFoundException: " + 
121
             "unable to determine metacat URL from SystemUtil.getServletURL()");
122
    }
123

    
124
    loadCatalog();
125
  }
126

    
127
  
128
  /* Class methods */
129
  
130
  /**
131
   * Use the current date as the basis for the resumptiontoken
132
   * 
133
   * @return a long integer version of the current time
134
   */
135
  private synchronized static String getRSName() {
136
    Date now = new Date();
137
    return Long.toString(now.getTime());
138
  }
139

    
140
  
141
  /* Instance methods */
142

    
143
  
144
  /**
145
   * close the repository
146
   */
147
  public void close() {
148
  }
149

    
150

    
151
  /**
152
   * Utility method to construct a Record object for a specified metadataFormat
153
   * from a native record
154
   * 
155
   * @param nativeItem
156
   *          native item from the dataase
157
   * @param metadataPrefix
158
   *          the desired metadataPrefix for performing the crosswalk
159
   * @return the <record/> String
160
   * @exception CannotDisseminateFormatException
161
   *              the record is not available for the specified metadataPrefix.
162
   */
163
  private String constructRecord(HashMap nativeItem, String metadataPrefix)
164
      throws CannotDisseminateFormatException {
165
    String schemaURL = null;
166
    Iterator setSpecs = getSetSpecs(nativeItem);
167
    Iterator abouts = getAbouts(nativeItem);
168

    
169
    if (metadataPrefix != null) {
170
      if ((schemaURL = getCrosswalks().getSchemaURL(metadataPrefix)) == null)
171
        throw new CannotDisseminateFormatException(metadataPrefix);
172
    }
173
    
174
    RecordFactory recordFactory = getRecordFactory();
175
    String recordString = recordFactory.create(nativeItem, schemaURL, 
176
                                              metadataPrefix, setSpecs, abouts);
177
    return recordString;
178
  }
179

    
180

    
181
  private String date2OAIDatestamp(Date date) {
182
    return dateFormatter.format(date);
183
  }
184

    
185

    
186
  /**
187
   * get an Iterator containing the abouts for the nativeItem
188
   * 
189
   * @param rs
190
   *          ResultSet containing the nativeItem
191
   * @return an Iterator containing the list of about values for this nativeItem
192
   */
193
  private Iterator getAbouts(HashMap nativeItem) {
194
    return null;
195
  }
196

    
197

    
198
  /**
199
   * Returns a connection to the database. Opens the connection if a connection
200
   * has not already been made previously.
201
   * 
202
   * @return  conn  the database Connection object
203
   */
204
  public Connection getConnection() {
205
    Connection conn = null;
206
    
207
    try {
208
      Class.forName(metacatDBDriver);
209
    }
210
    catch (ClassNotFoundException e) {
211
      logger.error("Can't load driver " + e);
212
      return conn;
213
    } 
214

    
215
    // Make the database connection
216
    try {
217
      conn = DriverManager.getConnection(metacatDBURL, metacatDBUser, 
218
                                           metacatDBPassword);
219

    
220
      // If a SQLWarning object is available, print its warning(s).
221
      // There may be multiple warnings chained.
222
      SQLWarning warn = conn.getWarnings();
223
      
224
      if (warn != null) {
225
        while (warn != null) {
226
          logger.warn("SQLState: " + warn.getSQLState());
227
          logger.warn("Message:  " + warn.getMessage());
228
          logger.warn("Vendor: " + warn.getErrorCode());
229
          warn = warn.getNextWarning();
230
        }
231
      }
232
    }
233
    catch (SQLException e) {
234
      logger.error("Database access failed " + e);
235
    }
236
    
237
    return conn;
238
  }
239

    
240

    
241
  /**
242
   * Get the most recent date that the xml_documents table was updated
243
   * @return
244
   */
245
  public String getMaxDateUpdated() {
246
    String maxDateUpdated = null;
247
    String query = 
248
              "SELECT MAX(date_updated) AS max_date_updated FROM xml_documents";
249
    Statement stmt;
250

    
251
    try {
252
      Connection conn = getConnection();    
253
      if (conn != null) {
254
        stmt = conn.createStatement();                          
255
        ResultSet rs = stmt.executeQuery(query);
256
        while (rs.next()) {
257
          maxDateUpdated = rs.getDate("max_date_updated").toString();
258
        }
259
        stmt.close();   
260
        conn.close();
261
      }
262
    }
263
    catch(SQLException e) {
264
      logger.error("SQLException: " + e.getMessage());
265
    }
266
    
267
    return maxDateUpdated;
268
  }
269
  
270
  
271
  /**
272
   * Get a document from Metacat.
273
   * 
274
   * @param docid  the docid of the document to read
275
   * 
276
   * @return recordMap       a HashMap holding the document contents
277
   * 
278
   * @throws IOException
279
   */
280
  private HashMap<String, String> getMetacatDocument(String docid) 
281
      throws IOException {
282
    HashMap<String, String> recordMap = getNativeHeader(docid);
283
    
284
    if (recordMap == null) {
285
      return null;
286
    } 
287
    else {
288
      try {
289
        /* Perform a Metacat read operation on this docid */
290
        Metacat metacat = MetacatFactory.createMetacatConnection(metacatURL);
291
        Reader reader = metacat.read(docid);
292
        StringBuffer stringBuffer = IOUtil.getAsStringBuffer(reader, true);
293
        String emlString = stringBuffer.toString();
294
        recordMap.put("recordBytes", emlString);
295
      }
296
      catch (MetacatInaccessibleException e) {
297
        logger.error("MetacatInaccessibleException:\n" + e.getMessage());
298
      }
299
      catch (MetacatException e) {
300
        logger.error("MetacatException:\n" + e.getMessage());
301
      }
302
      catch (DocumentNotFoundException e) {
303
        logger.error("DocumentNotFoundException:\n" + e.getMessage());
304
      }
305
      catch (InsufficientKarmaException e) {
306
        logger.error("InsufficientKarmaException:\n" + e.getMessage());
307
      }
308
      catch (IOException e) {
309
        logger.error("Error reading EML document from metacat:\n" + 
310
                     e.getMessage()
311
                    );
312
      }
313
    }
314
    
315
    return recordMap;
316
  }
317

    
318

    
319
  private HashMap<String, String> getNativeHeader(String localIdentifier) {
320
    HashMap<String, String> recordMap = null;
321
    
322
    if (dateMap.containsKey(localIdentifier)) {
323
      recordMap = new HashMap<String, String>();
324
      recordMap.put("localIdentifier", localIdentifier);
325
      recordMap.put("lastModified", dateMap.get(localIdentifier));
326
      return recordMap;
327
    }
328
    
329
    return recordMap;
330
  }
331

    
332

    
333
  /**
334
   * Retrieve the specified metadata for the specified oaiIdentifier
335
   * 
336
   * @param oaiIdentifier
337
   *          the OAI identifier
338
   * @param metadataPrefix
339
   *          the OAI metadataPrefix
340
   * @return the Record object containing the result.
341
   * @exception CannotDisseminateFormatException
342
   *              signals an http status code 400 problem
343
   * @exception IdDoesNotExistException
344
   *              signals an http status code 404 problem
345
   * @exception OAIInternalServerError
346
   *              signals an http status code 500 problem
347
   */
348
  public String getRecord(String oaiIdentifier, String metadataPrefix)
349
      throws IdDoesNotExistException, CannotDisseminateFormatException,
350
             OAIInternalServerError {
351
    HashMap<String, String> nativeItem = null;
352
    
353
    try {
354
      RecordFactory recordFactory = getRecordFactory();
355
      String localIdentifier = recordFactory.fromOAIIdentifier(oaiIdentifier);
356
      nativeItem = getMetacatDocument(localIdentifier);
357
      if (nativeItem == null) throw new IdDoesNotExistException(oaiIdentifier);
358
      return constructRecord(nativeItem, metadataPrefix);
359
    } 
360
    catch (IOException e) {
361
      e.printStackTrace();
362
      throw new OAIInternalServerError("Database Failure");
363
    }
364
  }
365

    
366

    
367
  /**
368
   * Retrieve a list of schemaLocation values associated with the specified
369
   * oaiIdentifier.
370
   * 
371
   * We get passed the ID for a record and are supposed to return a list of the
372
   * formats that we can deliver the record in. Since we are assuming that all
373
   * the records in the directory have the same format, the response to this is
374
   * static;
375
   * 
376
   * @param oaiIdentifier       the OAI identifier
377
   * 
378
   * @return a Vector containing schemaLocation Strings
379
   * 
380
   * @exception OAIBadRequestException
381
   *              signals an http status code 400 problem
382
   * @exception OAINotFoundException
383
   *              signals an http status code 404 problem
384
   * @exception OAIInternalServerError
385
   *              signals an http status code 500 problem
386
   */
387
  public Vector getSchemaLocations(String oaiIdentifier)
388
      throws IdDoesNotExistException, OAIInternalServerError,
389
      NoMetadataFormatsException {
390
    HashMap<String, String> nativeItem = null;
391
    
392
    try {
393
      String localIdentifier = getRecordFactory().fromOAIIdentifier(
394
          oaiIdentifier);
395
      nativeItem = getMetacatDocument(localIdentifier);
396
    } 
397
    catch (IOException e) {
398
      e.printStackTrace();
399
      throw new OAIInternalServerError("Database Failure");
400
    }
401

    
402
    if (nativeItem != null) {
403
      RecordFactory recordFactory = getRecordFactory();
404
      return recordFactory.getSchemaLocations(nativeItem);
405
    } 
406
    else {
407
      throw new IdDoesNotExistException(oaiIdentifier);
408
    }
409
  }
410

    
411

    
412
  /**
413
   * get an Iterator containing the setSpecs for the nativeItem
414
   * 
415
   * @param rs
416
   *          ResultSet containing the nativeItem
417
   * @return an Iterator containing the list of setSpec values for this
418
   *         nativeItem
419
   */
420
  private Iterator getSetSpecs(HashMap nativeItem) {
421
    return null;
422
  }
423

    
424

    
425
  /**
426
   * Override this method if some files exist in the filesystem that aren't
427
   * metadata records.
428
   * 
429
   * @param child
430
   *          the File to be investigated
431
   * @return true if it contains metadata, false otherwise
432
   */
433
  protected boolean isMetadataFile(File child) {
434
    return true;
435
  }
436

    
437

    
438
  /**
439
   * Retrieve a list of Identifiers that satisfy the criteria parameters
440
   * 
441
   * @param from
442
   *          beginning date in the form of YYYY-MM-DD or null if earliest date
443
   *          is desired
444
   * @param until
445
   *          ending date in the form of YYYY-MM-DD or null if latest date is
446
   *          desired
447
   * @param set
448
   *          set name or null if no set is desired
449
   * @return a Map object containing an optional "resumptionToken" key/value
450
   *         pair and an "identifiers" Map object. The "identifiers" Map
451
   *         contains OAI identifier keys with corresponding values of "true" or
452
   *         null depending on whether the identifier is deleted or not.
453
   * @exception OAIBadRequestException
454
   *              signals an http status code 400 problem
455
   */
456
  public Map listIdentifiers(String from, String until, String set,
457
      String metadataPrefix) throws NoItemsMatchException {
458
    purge(); // clean out old resumptionTokens
459
    Map listIdentifiersMap = new HashMap();
460
    ArrayList headers = new ArrayList();
461
    ArrayList identifiers = new ArrayList();
462
    Iterator iterator = dateMap.entrySet().iterator();
463
    int numRows = dateMap.entrySet().size();
464
    int count = 0;
465
    RecordFactory recordFactory = getRecordFactory();
466
    while (count < maxListSize && iterator.hasNext()) {
467
      Map.Entry entryDateMap = (Map.Entry) iterator.next();
468
      String fileDate = (String) entryDateMap.getValue();
469
      if (fileDate.compareTo(from) >= 0 && fileDate.compareTo(until) <= 0) {
470
        String key = (String) entryDateMap.getKey();
471
        HashMap nativeHeader = getNativeHeader(key);
472
        String[] header = recordFactory.createHeader(nativeHeader);
473
        headers.add(header[0]);
474
        identifiers.add(header[1]);
475
        count++;
476
      }
477
    }
478

    
479
    if (count == 0)
480
      throw new NoItemsMatchException();
481

    
482
    /* decide if you're done */
483
    if (iterator.hasNext()) {
484
      String 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(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 line
502
       * after it that I've commented out.
503
       *****************************************************************/
504
      listIdentifiersMap.put("resumptionMap", getResumptionMap(
505
          resumptionTokenSb.toString(), numRows, 0));
506
      // listIdentifiersMap.put("resumptionMap",
507
      // getResumptionMap(resumptionTokenSb.toString()));
508
    }
509
    listIdentifiersMap.put("headers", headers.iterator());
510
    listIdentifiersMap.put("identifiers", identifiers.iterator());
511
    return listIdentifiersMap;
512
  }
513

    
514

    
515
  /**
516
   * Retrieve the next set of Identifiers associated with the resumptionToken
517
   * 
518
   * @param resumptionToken
519
   *          implementation-dependent format taken from the previous
520
   *          listIdentifiers() Map result.
521
   * @return a Map object containing an optional "resumptionToken" key/value
522
   *         pair and an "identifiers" Map object. The "identifiers" Map
523
   *         contains OAI identifier keys with corresponding values of "true" or
524
   *         null depending on whether the identifier is deleted or not.
525
   * @exception OAIBadRequestException
526
   *              signals an http status code 400 problem
527
   */
528
  public Map listIdentifiers(String resumptionToken)
529
      throws BadResumptionTokenException {
530
    purge(); // clean out old resumptionTokens
531
    Map listIdentifiersMap = new HashMap();
532
    ArrayList headers = new ArrayList();
533
    ArrayList identifiers = new ArrayList();
534

    
535
    /**********************************************************************
536
     * parse your resumptionToken and look it up in the resumptionResults, if
537
     * necessary
538
     **********************************************************************/
539
    StringTokenizer tokenizer = new StringTokenizer(resumptionToken, ":");
540
    String resumptionId;
541
    int oldCount;
542
    String metadataPrefix;
543
    int numRows;
544
    try {
545
      resumptionId = tokenizer.nextToken();
546
      oldCount = Integer.parseInt(tokenizer.nextToken());
547
      numRows = Integer.parseInt(tokenizer.nextToken());
548
      metadataPrefix = tokenizer.nextToken();
549
    } catch (NoSuchElementException e) {
550
      throw new BadResumptionTokenException();
551
    }
552

    
553
    /* Get some more records from your database */
554
    Iterator iterator = (Iterator) resumptionResults.remove(resumptionId);
555
    if (iterator == null) {
556
      System.out
557
          .println("MetacatCatalog.listIdentifiers(): reuse of old resumptionToken?");
558
      iterator = dateMap.entrySet().iterator();
559
      for (int i = 0; i < oldCount; ++i)
560
        iterator.next();
561
    }
562

    
563
    /* load the headers and identifiers ArrayLists. */
564
    int count = 0;
565
    while (count < maxListSize && iterator.hasNext()) {
566
      Map.Entry entryDateMap = (Map.Entry) iterator.next();
567
      HashMap nativeHeader = getNativeHeader((String) entryDateMap.getKey());
568
      String[] header = getRecordFactory().createHeader(nativeHeader);
569
      headers.add(header[0]);
570
      identifiers.add(header[1]);
571
      count++;
572
    }
573

    
574
    /* decide if you're done. */
575
    if (iterator.hasNext()) {
576
      resumptionId = getRSName();
577
      resumptionResults.put(resumptionId, iterator);
578

    
579
      /*****************************************************************
580
       * Construct the resumptionToken String however you see fit.
581
       *****************************************************************/
582
      StringBuffer resumptionTokenSb = new StringBuffer();
583
      resumptionTokenSb.append(resumptionId);
584
      resumptionTokenSb.append(":");
585
      resumptionTokenSb.append(Integer.toString(oldCount + count));
586
      resumptionTokenSb.append(":");
587
      resumptionTokenSb.append(Integer.toString(numRows));
588
      resumptionTokenSb.append(":");
589
      resumptionTokenSb.append(metadataPrefix);
590

    
591
      /*****************************************************************
592
       * Use the following line if you wish to include the optional
593
       * resumptionToken attributes in the response. Otherwise, use the line
594
       * after it that I've commented out.
595
       *****************************************************************/
596
      listIdentifiersMap.put("resumptionMap", getResumptionMap(
597
          resumptionTokenSb.toString(), numRows, oldCount));
598
      // listIdentifiersMap.put("resumptionMap",
599
      // getResumptionMap(resumptionTokenSb.toString()));
600
    }
601

    
602
    listIdentifiersMap.put("headers", headers.iterator());
603
    listIdentifiersMap.put("identifiers", identifiers.iterator());
604
    return listIdentifiersMap;
605
  }
606

    
607

    
608
  /**
609
   * Run a query of the Metacat database to load the catalog of EML documents.
610
   * For each EML document, we store its 'docid', 'doctype', and 'date_updated'
611
   * values.
612
   */
613
  public void loadCatalog() {
614
    String query = 
615
      "SELECT docid, doctype, date_updated " +
616
      "FROM xml_documents " +
617
      "WHERE doctype like 'eml://ecoinformatics.org/eml-2%'";
618
    Statement stmt;
619

    
620
    try {
621
      Connection conn = getConnection();
622
      
623
      if (conn != null) {
624
        stmt = conn.createStatement();                          
625
        ResultSet rs = stmt.executeQuery(query);
626

    
627
        while (rs.next()) {
628
          String docid = rs.getString("docid");
629
          String doctype = rs.getString("doctype");
630
          String dateUpdated = rs.getDate("date_updated").toString();
631
          docTypeMap.put(docid, doctype);
632
          dateMap.put(docid, dateUpdated);
633
        }
634

    
635
        stmt.close();   
636
        conn.close();
637
      }
638
    }
639
    catch(SQLException e) {
640
      logger.error("SQLException: " + e.getMessage());
641
    }
642
  }
643
  
644

    
645
  /**
646
   * Retrieve a list of records that satisfy the specified criteria
647
   * 
648
   * @param from
649
   *          beginning date in the form of YYYY-MM-DD or null if earliest date
650
   *          is desired
651
   * @param until
652
   *          ending date in the form of YYYY-MM-DD or null if latest date is
653
   *          desired
654
   * @param set
655
   *          set name or null if no set is desired
656
   * @param metadataPrefix
657
   *          the OAI metadataPrefix
658
   * @return a Map object containing an optional "resumptionToken" key/value
659
   *         pair and a "records" Iterator object. The "records" Iterator
660
   *         contains a set of Records objects.
661
   * @exception OAIBadRequestException
662
   *              signals an http status code 400 problem
663
   * @exception OAIInternalServerError
664
   *              signals an http status code 500 problem
665
   */
666
  public Map listRecords(String from, String until, String set,
667
      String metadataPrefix) throws CannotDisseminateFormatException,
668
      OAIInternalServerError, NoItemsMatchException {
669
    purge(); // clean out old resumptionTokens
670
    Map listRecordsMap = new HashMap();
671
    ArrayList records = new ArrayList();
672
    Iterator iterator = dateMap.entrySet().iterator();
673
    int numRows = dateMap.entrySet().size();
674
    int count = 0;
675
    
676
    while (count < maxListSize && iterator.hasNext()) {
677
      Map.Entry entryDateMap = (Map.Entry) iterator.next();
678
      String fileDate = (String) entryDateMap.getValue();
679
      
680
      if (fileDate.compareTo(from) >= 0 && fileDate.compareTo(until) <= 0) {
681
        try {
682
          String localIdentifier = (String) entryDateMap.getKey();
683
          HashMap nativeItem = getMetacatDocument(localIdentifier);
684
          String record = constructRecord(nativeItem, metadataPrefix);
685
          records.add(record);
686
          count++;
687
        } 
688
        catch (IOException e) {
689
          e.printStackTrace();
690
          throw new OAIInternalServerError(e.getMessage());
691
        }
692
      }
693
    }
694

    
695
    if (count == 0)
696
      throw new NoItemsMatchException();
697

    
698
    /* decide if you're done */
699
    if (iterator.hasNext()) {
700
      String resumptionId = getRSName();
701
      resumptionResults.put(resumptionId, iterator);
702

    
703
      /*****************************************************************
704
       * Construct the resumptionToken String however you see fit.
705
       *****************************************************************/
706
      StringBuffer resumptionTokenSb = new StringBuffer();
707
      resumptionTokenSb.append(resumptionId);
708
      resumptionTokenSb.append(":");
709
      resumptionTokenSb.append(Integer.toString(count));
710
      resumptionTokenSb.append(":");
711
      resumptionTokenSb.append(Integer.toString(numRows));
712
      resumptionTokenSb.append(":");
713
      resumptionTokenSb.append(metadataPrefix);
714

    
715
      /*****************************************************************
716
       * Use the following line if you wish to include the optional
717
       * resumptionToken attributes in the response. Otherwise, use the line
718
       * after it that I've commented out.
719
       *****************************************************************/
720
      listRecordsMap.put("resumptionMap", getResumptionMap(resumptionTokenSb
721
          .toString(), numRows, 0));
722
      // listRecordsMap.put("resumptionMap",
723
      // getResumptionMap(resumptionTokenSb.toString()));
724
    }
725
    listRecordsMap.put("records", records.iterator());
726
    return listRecordsMap;
727
  }
728

    
729

    
730
  /**
731
   * Retrieve the next set of records associated with the resumptionToken
732
   * 
733
   * @param resumptionToken
734
   *          implementation-dependent format taken from the previous
735
   *          listRecords() Map result.
736
   * @return a Map object containing an optional "resumptionToken" key/value
737
   *         pair and a "records" Iterator object. The "records" Iterator
738
   *         contains a set of Records objects.
739
   * @exception OAIBadRequestException
740
   *              signals an http status code 400 problem
741
   */
742
  public Map listRecords(String resumptionToken)
743
      throws BadResumptionTokenException {
744
    purge(); // clean out old resumptionTokens
745
    Map listRecordsMap = new HashMap();
746
    ArrayList records = new ArrayList();
747

    
748
    /**********************************************************************
749
     * parse your resumptionToken and look it up in the resumptionResults, if
750
     * necessary
751
     **********************************************************************/
752
    StringTokenizer tokenizer = new StringTokenizer(resumptionToken, ":");
753
    String resumptionId;
754
    int oldCount;
755
    String metadataPrefix;
756
    int numRows;
757
    
758
    try {
759
      resumptionId = tokenizer.nextToken();
760
      oldCount = Integer.parseInt(tokenizer.nextToken());
761
      numRows = Integer.parseInt(tokenizer.nextToken());
762
      metadataPrefix = tokenizer.nextToken();
763
    } 
764
    catch (NoSuchElementException e) {
765
      throw new BadResumptionTokenException();
766
    }
767

    
768
    /* Get some more records from your database */
769
    Iterator iterator = (Iterator) resumptionResults.remove(resumptionId);
770
    
771
    if (iterator == null) {
772
      System.out
773
          .println("MetacatCatalog.listRecords(): reuse of old resumptionToken?");
774
      iterator = dateMap.entrySet().iterator();
775
      for (int i = 0; i < oldCount; ++i)
776
        iterator.next();
777
    }
778

    
779
    /* load the records ArrayLists. */
780
    int count = 0;
781
    
782
    while (count < maxListSize && iterator.hasNext()) {
783
      Map.Entry entryDateMap = (Map.Entry) iterator.next();
784
      
785
      try {
786
        String localIdentifier = (String) entryDateMap.getKey();
787
        HashMap nativeItem = getMetacatDocument(localIdentifier);
788
        String record = constructRecord(nativeItem, metadataPrefix);
789
        records.add(record);
790
        count++;
791
      } 
792
      catch (CannotDisseminateFormatException e) {
793
        /* the client hacked the resumptionToken beyond repair */
794
        throw new BadResumptionTokenException();
795
      } 
796
      catch (IOException e) {
797
        /* the file is probably missing */
798
        throw new BadResumptionTokenException();
799
      }
800
    }
801

    
802
    /* decide if you're done. */
803
    if (iterator.hasNext()) {
804
      resumptionId = getRSName();
805
      resumptionResults.put(resumptionId, iterator);
806

    
807
      /*****************************************************************
808
       * Construct the resumptionToken String however you see fit.
809
       *****************************************************************/
810
      StringBuffer resumptionTokenSb = new StringBuffer();
811
      resumptionTokenSb.append(resumptionId);
812
      resumptionTokenSb.append(":");
813
      resumptionTokenSb.append(Integer.toString(oldCount + count));
814
      resumptionTokenSb.append(":");
815
      resumptionTokenSb.append(Integer.toString(numRows));
816
      resumptionTokenSb.append(":");
817
      resumptionTokenSb.append(metadataPrefix);
818

    
819
      /*****************************************************************
820
       * Use the following line if you wish to include the optional
821
       * resumptionToken attributes in the response. Otherwise, use the line
822
       * after it that I've commented out.
823
       *****************************************************************/
824
      listRecordsMap.put("resumptionMap", 
825
                         getResumptionMap(resumptionTokenSb.toString(), 
826
                         numRows, 
827
                         oldCount)
828
                        );
829
      // listRecordsMap.put("resumptionMap",
830
      // getResumptionMap(resumptionTokenSb.toString()));
831
    }
832

    
833
    listRecordsMap.put("records", records.iterator());
834
    
835
    return listRecordsMap;
836
  }
837

    
838

    
839
  public Map listSets() throws NoSetHierarchyException {
840
    throw new NoSetHierarchyException();
841
    // Map listSetsMap = new HashMap();
842
    // listSetsMap.put("sets", setsList.iterator());
843
    // return listSetsMap;
844
  }
845

    
846

    
847
  public Map listSets(String resumptionToken)
848
      throws BadResumptionTokenException {
849
    throw new BadResumptionTokenException();
850
  }
851

    
852

    
853
  /**
854
   * Purge tokens that are older than the time-to-live.
855
   */
856
  private void purge() {
857
    ArrayList old = new ArrayList();
858
    Date then, now = new Date();
859
    Iterator keySet = resumptionResults.keySet().iterator();
860
    String key;
861

    
862
    while (keySet.hasNext()) {
863
      key = (String) keySet.next();
864
      then = new Date(Long.parseLong(key) + getMillisecondsToLive());
865
      if (now.after(then)) {
866
        old.add(key);
867
      }
868
    }
869
    Iterator iterator = old.iterator();
870
    while (iterator.hasNext()) {
871
      key = (String) iterator.next();
872
      resumptionResults.remove(key);
873
    }
874
  }
875

    
876
}
(1-1/2)