Project

General

Profile

1 4943 costa
/**
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 5066 leinfelder
import java.io.InputStreamReader;
16 4943 costa
import java.io.Reader;
17
import java.sql.Connection;
18
import java.sql.DriverManager;
19
import java.sql.ResultSet;
20
import java.sql.SQLException;
21
import java.sql.SQLWarning;
22
import java.sql.Statement;
23
import java.text.SimpleDateFormat;
24
import java.util.ArrayList;
25
import java.util.Date;
26
import java.util.HashMap;
27
import java.util.Iterator;
28
import java.util.Map;
29
import java.util.NoSuchElementException;
30
import java.util.Properties;
31
import java.util.StringTokenizer;
32
import java.util.Vector;
33
34
import org.apache.log4j.Logger;
35
36
import edu.ucsb.nceas.metacat.client.DocumentNotFoundException;
37
import edu.ucsb.nceas.metacat.client.InsufficientKarmaException;
38
import edu.ucsb.nceas.metacat.client.Metacat;
39
import edu.ucsb.nceas.metacat.client.MetacatException;
40
import edu.ucsb.nceas.metacat.client.MetacatFactory;
41
import edu.ucsb.nceas.metacat.client.MetacatInaccessibleException;
42
import edu.ucsb.nceas.metacat.oaipmh.provider.server.OAIHandler;
43
import edu.ucsb.nceas.metacat.util.SystemUtil;
44
import edu.ucsb.nceas.utilities.IOUtil;
45
import edu.ucsb.nceas.utilities.PropertyNotFoundException;
46
47
import ORG.oclc.oai.server.catalog.AbstractCatalog;
48
import ORG.oclc.oai.server.catalog.RecordFactory;
49
import ORG.oclc.oai.server.verb.BadResumptionTokenException;
50
import ORG.oclc.oai.server.verb.CannotDisseminateFormatException;
51
import ORG.oclc.oai.server.verb.IdDoesNotExistException;
52
import ORG.oclc.oai.server.verb.NoItemsMatchException;
53
import ORG.oclc.oai.server.verb.NoMetadataFormatsException;
54
import ORG.oclc.oai.server.verb.NoSetHierarchyException;
55
import ORG.oclc.oai.server.verb.OAIInternalServerError;
56
57
58
/**
59
 * MetacatCatalog is an implementation of AbstractCatalog interface.
60
 *
61
 * @author Ralph LeVan, OCLC Online Computer Library Center
62
 */
63
64
public class MetacatCatalog extends AbstractCatalog {
65
66
  /* Class fields */
67
68
  private static final Logger logger = Logger.getLogger(MetacatCatalog.class);
69 4996 costa
  private static String refreshDate = null;
70 4943 costa
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
  protected String homeDir;
82
  private HashMap<String, String> dateMap = new HashMap<String, String>();
83 4946 costa
  private HashMap<String, String> filteredDateMap = null;
84 4943 costa
  private HashMap<String, String> docTypeMap = new HashMap<String, String>();
85
  private HashMap resumptionResults = new HashMap();
86
  private int maxListSize;
87
88 4945 costa
  /*
89
   * QUERY string to find all eml-2.x.y documents in the Metacat database
90
   * that are publicly accessible
91
   */
92
  private final String QUERY =
93 6744 leinfelder
  "SELECT xd.docid, xd.doctype, xd.date_updated " +
94
  "FROM xml_documents xd, identifier id " +
95
  "WHERE xd.doctype like 'eml://ecoinformatics.org/eml-2%' " +
96
  " AND xd.docid = id.docid " +
97
  " AND xd.rev = id.rev " +
98
  // ALLOW rule
99
  " AND id.guid IN " +
100
  "     (SELECT guid " +
101
  "     FROM xml_access " +
102
  "		AND lower(principal_name) = 'public' " +
103
  " 	AND perm_type = 'allow' " +
104
  " 	AND permission > 3" +
105
  "		) " +
106
  // DENY rules?
107
  " AND id.guid NOT IN " +
108
  "     (SELECT guid " +
109
  "     FROM xml_access " +
110
  "     WHERE lower(principal_name) = 'public' " +
111
  "		AND perm_type = 'deny' " +
112
  "		AND perm_order ='allowFirst' " +
113
  "		AND permission > 3 " +
114
  "     ) ";
115 4943 costa
116
117 4945 costa
/* Constructors */
118
119 4943 costa
  public MetacatCatalog(Properties properties) {
120
    String errorStr;
121
    String temp;
122
123
    temp = properties.getProperty("oaipmh.maxListSize");
124
    if (temp == null) {
125
      errorStr = "oaipmh.maxListSize is missing from the properties file";
126
      throw new IllegalArgumentException(errorStr);
127
    }
128
    maxListSize = Integer.parseInt(temp);
129
130
    metacatDBDriver = properties.getProperty("database.driver");
131
    metacatDBURL = properties.getProperty("database.connectionURI");
132
    metacatDBUser = properties.getProperty("database.user");
133
    metacatDBPassword = properties.getProperty("database.password");
134
135
    try {
136
      if (OAIHandler.isIntegratedWithMetacat()) {
137
        metacatURL = SystemUtil.getServletURL();
138
      }
139
      else {
140 4945 costa
        metacatURL = properties.getProperty("test.metacatUrl");
141 4943 costa
      }
142
143
      logger.warn("metacatURL: " + metacatURL);
144
    }
145
    catch (PropertyNotFoundException e) {
146
      logger.error("PropertyNotFoundException: " +
147
             "unable to determine metacat URL from SystemUtil.getServletURL()");
148
    }
149
150
    loadCatalog();
151
  }
152
153
154
  /* Class methods */
155
156
  /**
157
   * Use the current date as the basis for the resumptiontoken
158
   *
159
   * @return a long integer version of the current time
160
   */
161
  private synchronized static String getRSName() {
162
    Date now = new Date();
163
    return Long.toString(now.getTime());
164
  }
165
166
167
  /* Instance methods */
168
169
170
  /**
171
   * close the repository
172
   */
173
  public void close() {
174
  }
175
176
177
  /**
178
   * Utility method to construct a Record object for a specified metadataFormat
179
   * from a native record
180
   *
181
   * @param nativeItem
182
   *          native item from the dataase
183
   * @param metadataPrefix
184
   *          the desired metadataPrefix for performing the crosswalk
185
   * @return the <record/> String
186
   * @exception CannotDisseminateFormatException
187
   *              the record is not available for the specified metadataPrefix.
188
   */
189
  private String constructRecord(HashMap nativeItem, String metadataPrefix)
190
      throws CannotDisseminateFormatException {
191
    String schemaURL = null;
192
    Iterator setSpecs = getSetSpecs(nativeItem);
193
    Iterator abouts = getAbouts(nativeItem);
194
195
    if (metadataPrefix != null) {
196
      if ((schemaURL = getCrosswalks().getSchemaURL(metadataPrefix)) == null)
197
        throw new CannotDisseminateFormatException(metadataPrefix);
198
    }
199
200
    RecordFactory recordFactory = getRecordFactory();
201
    String recordString = recordFactory.create(nativeItem, schemaURL,
202
                                              metadataPrefix, setSpecs, abouts);
203
    return recordString;
204
  }
205 4996 costa
206
207 4946 costa
  /**
208
   * Using the original dateMap catalog, produce a filtered dateMap catalog
209
   * consisting of only those entries that match the 'from', 'until', and
210
   * 'metadataPrefix' criteria.
211
   *
212
   * @param from                 the from date, e.g. "2008-06-01"
213
   * @param until                the until date, e.g. "2009-01-01"
214
   * @param metadataPrefix       the metadataPrefix value, e.g. "oai_dc"
215
   *
216
   * @return   aDateMap, a HashMap containing only the matched entries.
217
   */
218
  private HashMap<String, String> filterDateMap(String from, String until,
219
      String metadataPrefix) {
220 4996 costa
221
    if (shouldRefreshCatalog()) {
222
      loadCatalog();
223
    }
224
225 4946 costa
    HashMap<String, String> aDateMap = new HashMap<String, String>();
226
    Iterator iterator = dateMap.entrySet().iterator();
227
228
    while (iterator.hasNext()) {
229
      Map.Entry entryDateMap = (Map.Entry) iterator.next();
230
      String dateUpdated = (String) entryDateMap.getValue();
231
232
      /*
233
       * First filter catalog entries based on whether their date updated falls
234
       * within the 'from' and 'until' parameters.
235
       */
236
      if (dateUpdated.compareTo(from) >= 0 && dateUpdated.compareTo(until) <= 0)
237
      {
238
        String docid = (String) entryDateMap.getKey();
239
        HashMap<String, String> nativeHeader = getNativeHeader(docid);
240
        String doctype = nativeHeader.get("doctype");
241
242
        /*
243
         * Next filter catalog entries based on Metacat doctype as compared to
244
         * OAI-PMH metadataPrefix.
245
         */
246
        if (isIncludedDoctype(doctype, metadataPrefix)) {
247
          aDateMap.put(docid, dateUpdated);
248
        }
249
      }
250
251
    }
252
253
    return aDateMap;
254 4943 costa
  }
255
256
257
  /**
258
   * get an Iterator containing the abouts for the nativeItem
259
   *
260
   * @param rs
261
   *          ResultSet containing the nativeItem
262
   * @return an Iterator containing the list of about values for this nativeItem
263
   */
264
  private Iterator getAbouts(HashMap nativeItem) {
265
    return null;
266
  }
267
268
269
  /**
270
   * Returns a connection to the database. Opens the connection if a connection
271
   * has not already been made previously.
272
   *
273
   * @return  conn  the database Connection object
274
   */
275
  public Connection getConnection() {
276
    Connection conn = null;
277
278
    try {
279
      Class.forName(metacatDBDriver);
280
    }
281
    catch (ClassNotFoundException e) {
282
      logger.error("Can't load driver " + e);
283
      return conn;
284
    }
285
286
    // Make the database connection
287
    try {
288
      conn = DriverManager.getConnection(metacatDBURL, metacatDBUser,
289
                                           metacatDBPassword);
290
291
      // If a SQLWarning object is available, print its warning(s).
292
      // There may be multiple warnings chained.
293
      SQLWarning warn = conn.getWarnings();
294
295
      if (warn != null) {
296
        while (warn != null) {
297
          logger.warn("SQLState: " + warn.getSQLState());
298
          logger.warn("Message:  " + warn.getMessage());
299
          logger.warn("Vendor: " + warn.getErrorCode());
300
          warn = warn.getNextWarning();
301
        }
302
      }
303
    }
304
    catch (SQLException e) {
305
      logger.error("Database access failed " + e);
306
    }
307
308
    return conn;
309
  }
310
311
312
  /**
313
   * Get the most recent date that the xml_documents table was updated
314
   * @return
315
   */
316
  public String getMaxDateUpdated() {
317
    String maxDateUpdated = null;
318
    String query =
319
              "SELECT MAX(date_updated) AS max_date_updated FROM xml_documents";
320
    Statement stmt;
321
322
    try {
323
      Connection conn = getConnection();
324
      if (conn != null) {
325
        stmt = conn.createStatement();
326
        ResultSet rs = stmt.executeQuery(query);
327
        while (rs.next()) {
328
          maxDateUpdated = rs.getDate("max_date_updated").toString();
329
        }
330
        stmt.close();
331
        conn.close();
332
      }
333
    }
334
    catch(SQLException e) {
335
      logger.error("SQLException: " + e.getMessage());
336
    }
337
338
    return maxDateUpdated;
339
  }
340
341
342
  /**
343
   * Get a document from Metacat.
344
   *
345
   * @param docid  the docid of the document to read
346
   *
347
   * @return recordMap       a HashMap holding the document contents
348
   *
349
   * @throws IOException
350
   */
351
  private HashMap<String, String> getMetacatDocument(String docid)
352
      throws IOException {
353
    HashMap<String, String> recordMap = getNativeHeader(docid);
354
355
    if (recordMap == null) {
356
      return null;
357
    }
358
    else {
359
      try {
360
        /* Perform a Metacat read operation on this docid */
361
        Metacat metacat = MetacatFactory.createMetacatConnection(metacatURL);
362 5066 leinfelder
        Reader reader = new InputStreamReader(metacat.read(docid));
363 4943 costa
        StringBuffer stringBuffer = IOUtil.getAsStringBuffer(reader, true);
364
        String emlString = stringBuffer.toString();
365
        recordMap.put("recordBytes", emlString);
366
      }
367
      catch (MetacatInaccessibleException e) {
368
        logger.error("MetacatInaccessibleException:\n" + e.getMessage());
369
      }
370
      catch (MetacatException e) {
371
        logger.error("MetacatException:\n" + e.getMessage());
372
      }
373
      catch (DocumentNotFoundException e) {
374
        logger.error("DocumentNotFoundException:\n" + e.getMessage());
375
      }
376
      catch (InsufficientKarmaException e) {
377
        logger.error("InsufficientKarmaException:\n" + e.getMessage());
378
      }
379
      catch (IOException e) {
380
        logger.error("Error reading EML document from metacat:\n" +
381
                     e.getMessage()
382
                    );
383
      }
384
    }
385
386
    return recordMap;
387
  }
388
389
390
  private HashMap<String, String> getNativeHeader(String localIdentifier) {
391
    HashMap<String, String> recordMap = null;
392
393
    if (dateMap.containsKey(localIdentifier)) {
394
      recordMap = new HashMap<String, String>();
395
      recordMap.put("localIdentifier", localIdentifier);
396
      recordMap.put("lastModified", dateMap.get(localIdentifier));
397 4944 costa
      recordMap.put("doctype", docTypeMap.get(localIdentifier));
398 4943 costa
      return recordMap;
399
    }
400
401
    return recordMap;
402
  }
403
404
405
  /**
406
   * Retrieve the specified metadata for the specified oaiIdentifier
407
   *
408
   * @param oaiIdentifier
409
   *          the OAI identifier
410
   * @param metadataPrefix
411
   *          the OAI metadataPrefix
412
   * @return the Record object containing the result.
413
   * @exception CannotDisseminateFormatException
414
   *              signals an http status code 400 problem
415
   * @exception IdDoesNotExistException
416
   *              signals an http status code 404 problem
417
   * @exception OAIInternalServerError
418
   *              signals an http status code 500 problem
419
   */
420
  public String getRecord(String oaiIdentifier, String metadataPrefix)
421 4996 costa
      throws IdDoesNotExistException,
422
             CannotDisseminateFormatException,
423
             OAIInternalServerError
424
  {
425 4943 costa
    HashMap<String, String> nativeItem = null;
426
427
    try {
428
      RecordFactory recordFactory = getRecordFactory();
429
      String localIdentifier = recordFactory.fromOAIIdentifier(oaiIdentifier);
430
      nativeItem = getMetacatDocument(localIdentifier);
431
      if (nativeItem == null) throw new IdDoesNotExistException(oaiIdentifier);
432
      return constructRecord(nativeItem, metadataPrefix);
433
    }
434
    catch (IOException e) {
435
      e.printStackTrace();
436
      throw new OAIInternalServerError("Database Failure");
437
    }
438
  }
439
440
441
  /**
442
   * Retrieve a list of schemaLocation values associated with the specified
443
   * oaiIdentifier.
444
   *
445
   * We get passed the ID for a record and are supposed to return a list of the
446
   * formats that we can deliver the record in. Since we are assuming that all
447
   * the records in the directory have the same format, the response to this is
448
   * static;
449
   *
450
   * @param oaiIdentifier       the OAI identifier
451
   *
452
   * @return a Vector containing schemaLocation Strings
453
   *
454
   * @exception OAIBadRequestException
455
   *              signals an http status code 400 problem
456
   * @exception OAINotFoundException
457
   *              signals an http status code 404 problem
458
   * @exception OAIInternalServerError
459
   *              signals an http status code 500 problem
460
   */
461
  public Vector getSchemaLocations(String oaiIdentifier)
462
      throws IdDoesNotExistException, OAIInternalServerError,
463
      NoMetadataFormatsException {
464
    HashMap<String, String> nativeItem = null;
465
466
    try {
467
      String localIdentifier = getRecordFactory().fromOAIIdentifier(
468
          oaiIdentifier);
469
      nativeItem = getMetacatDocument(localIdentifier);
470
    }
471
    catch (IOException e) {
472
      e.printStackTrace();
473
      throw new OAIInternalServerError("Database Failure");
474
    }
475
476
    if (nativeItem != null) {
477
      RecordFactory recordFactory = getRecordFactory();
478
      return recordFactory.getSchemaLocations(nativeItem);
479
    }
480
    else {
481
      throw new IdDoesNotExistException(oaiIdentifier);
482
    }
483
  }
484
485
486
  /**
487
   * get an Iterator containing the setSpecs for the nativeItem
488
   *
489
   * @param rs
490
   *          ResultSet containing the nativeItem
491
   * @return an Iterator containing the list of setSpec values for this
492
   *         nativeItem
493
   */
494
  private Iterator getSetSpecs(HashMap nativeItem) {
495
    return null;
496
  }
497
498
499
  /**
500 4946 costa
   * Should a document with the specified Metacat doctype be included in the
501
   * list of identifiers/records for the specified OAI-PMH metadataPrefix?
502
   *
503
   * @param doctype              e.g. "eml://ecoinformatics.org/eml-2.1.0"
504
   * @param metadataPrefix       e.g. "oai_dc", "eml-2.0.1", "eml-2.1.0"
505
   * @return
506
   */
507
  private boolean isIncludedDoctype(String doctype, String metadataPrefix) {
508
    boolean isIncluded = false;
509
510
    /*
511
     * If the metadataPrefix is "oai_dc", then include all catalog entries
512
     * in the list of identifiers. Else if the metadataPrefix is an EML
513
     * document type, then only include those catalog entries whose
514
     * document type matches that of the metadataPrefix.
515
     */
516
    if (doctype != null &&
517
        (metadataPrefix.equals("oai_dc") ||
518
         (doctype.startsWith("eml://ecoinformatics.org/eml-") &&
519
          doctype.endsWith(metadataPrefix)
520
         )
521
        )
522
       ) {
523
      isIncluded = true;
524
    }
525
526
    return isIncluded;
527
  }
528
529
530
  /**
531 4943 costa
   * Override this method if some files exist in the filesystem that aren't
532
   * metadata records.
533
   *
534
   * @param child
535
   *          the File to be investigated
536
   * @return true if it contains metadata, false otherwise
537
   */
538
  protected boolean isMetadataFile(File child) {
539
    return true;
540
  }
541 4946 costa
542
543 4943 costa
  /**
544
   * Retrieve a list of Identifiers that satisfy the criteria parameters
545
   *
546
   * @param from
547
   *          beginning date in the form of YYYY-MM-DD or null if earliest date
548
   *          is desired
549
   * @param until
550
   *          ending date in the form of YYYY-MM-DD or null if latest date is
551
   *          desired
552
   * @param set
553 4996 costa
   *          set name or null if no set is desired
554
   * @param metadataPrefix
555
   *          e.g. "oai_dc", "eml-2.0.1", "eml-2.1.0"
556
   *
557 4943 costa
   * @return a Map object containing an optional "resumptionToken" key/value
558
   *         pair and an "identifiers" Map object. The "identifiers" Map
559
   *         contains OAI identifier keys with corresponding values of "true" or
560
   *         null depending on whether the identifier is deleted or not.
561
   * @exception OAIBadRequestException
562
   *              signals an http status code 400 problem
563
   */
564
  public Map listIdentifiers(String from, String until, String set,
565 4996 costa
                             String metadataPrefix)
566
          throws NoItemsMatchException {
567 4943 costa
    purge(); // clean out old resumptionTokens
568 4946 costa
569
    Map<String, Object> listIdentifiersMap = new HashMap<String, Object>();
570
    ArrayList<String> headers = new ArrayList<String>();
571
    ArrayList<String> identifiers = new ArrayList<String>();
572
573
    filteredDateMap = filterDateMap(from, until, metadataPrefix);
574
575
    Iterator iterator = filteredDateMap.entrySet().iterator();
576
    int numRows = filteredDateMap.entrySet().size();
577 4943 costa
    int count = 0;
578
    RecordFactory recordFactory = getRecordFactory();
579 4946 costa
580 4943 costa
    while (count < maxListSize && iterator.hasNext()) {
581
      Map.Entry entryDateMap = (Map.Entry) iterator.next();
582 4946 costa
      String dateUpdated = (String) entryDateMap.getValue();
583
      String key = (String) entryDateMap.getKey();
584
      HashMap<String, String> nativeHeader = getNativeHeader(key);
585
      String[] headerArray = recordFactory.createHeader(nativeHeader);
586
587
     /*
588
      * header, e.g.
589
      *
590
      * <header>
591
      *   <identifier>urn:lsid:knb.ecoinformatics.org:knb-lter-gce:26</identifier>
592
      *   <datestamp>2009-03-11</datestamp>
593
      * </header>
594
      */
595
      String header = headerArray[0];
596
      headers.add(header);
597
598
      /*
599
       * identifier, e.g. urn:lsid:knb.ecoinformatics.org:knb-lter-gce:26
600
       */
601
      String identifier = headerArray[1];
602
      identifiers.add(identifier);
603
      count++;
604 4943 costa
    }
605
606 4946 costa
    if (count == 0) { throw new NoItemsMatchException(); }
607 4943 costa
608
    /* decide if you're done */
609
    if (iterator.hasNext()) {
610
      String resumptionId = getRSName();
611
      resumptionResults.put(resumptionId, iterator);
612
613
      /*****************************************************************
614
       * Construct the resumptionToken String however you see fit.
615
       *****************************************************************/
616
      StringBuffer resumptionTokenSb = new StringBuffer();
617
      resumptionTokenSb.append(resumptionId);
618
      resumptionTokenSb.append(":");
619
      resumptionTokenSb.append(Integer.toString(count));
620
      resumptionTokenSb.append(":");
621
      resumptionTokenSb.append(Integer.toString(numRows));
622
      resumptionTokenSb.append(":");
623
      resumptionTokenSb.append(metadataPrefix);
624
625
      /*****************************************************************
626
       * Use the following line if you wish to include the optional
627
       * resumptionToken attributes in the response. Otherwise, use the line
628
       * after it that I've commented out.
629
       *****************************************************************/
630
      listIdentifiersMap.put("resumptionMap", getResumptionMap(
631
          resumptionTokenSb.toString(), numRows, 0));
632
      // listIdentifiersMap.put("resumptionMap",
633
      // getResumptionMap(resumptionTokenSb.toString()));
634
    }
635 4946 costa
636 4943 costa
    listIdentifiersMap.put("headers", headers.iterator());
637
    listIdentifiersMap.put("identifiers", identifiers.iterator());
638 4946 costa
639 4943 costa
    return listIdentifiersMap;
640
  }
641
642
643
  /**
644
   * Retrieve the next set of Identifiers associated with the resumptionToken
645
   *
646
   * @param resumptionToken
647
   *          implementation-dependent format taken from the previous
648
   *          listIdentifiers() Map result.
649
   * @return a Map object containing an optional "resumptionToken" key/value
650
   *         pair and an "identifiers" Map object. The "identifiers" Map
651
   *         contains OAI identifier keys with corresponding values of "true" or
652
   *         null depending on whether the identifier is deleted or not.
653
   * @exception OAIBadRequestException
654
   *              signals an http status code 400 problem
655
   */
656
  public Map listIdentifiers(String resumptionToken)
657
      throws BadResumptionTokenException {
658
    purge(); // clean out old resumptionTokens
659
    Map listIdentifiersMap = new HashMap();
660
    ArrayList headers = new ArrayList();
661
    ArrayList identifiers = new ArrayList();
662
663
    /**********************************************************************
664
     * parse your resumptionToken and look it up in the resumptionResults, if
665
     * necessary
666
     **********************************************************************/
667
    StringTokenizer tokenizer = new StringTokenizer(resumptionToken, ":");
668
    String resumptionId;
669
    int oldCount;
670
    String metadataPrefix;
671
    int numRows;
672
    try {
673
      resumptionId = tokenizer.nextToken();
674
      oldCount = Integer.parseInt(tokenizer.nextToken());
675
      numRows = Integer.parseInt(tokenizer.nextToken());
676
      metadataPrefix = tokenizer.nextToken();
677
    } catch (NoSuchElementException e) {
678
      throw new BadResumptionTokenException();
679
    }
680
681
    /* Get some more records from your database */
682
    Iterator iterator = (Iterator) resumptionResults.remove(resumptionId);
683
    if (iterator == null) {
684
      System.out
685
          .println("MetacatCatalog.listIdentifiers(): reuse of old resumptionToken?");
686
      iterator = dateMap.entrySet().iterator();
687
      for (int i = 0; i < oldCount; ++i)
688
        iterator.next();
689
    }
690
691
    /* load the headers and identifiers ArrayLists. */
692
    int count = 0;
693
    while (count < maxListSize && iterator.hasNext()) {
694
      Map.Entry entryDateMap = (Map.Entry) iterator.next();
695
      HashMap nativeHeader = getNativeHeader((String) entryDateMap.getKey());
696
      String[] header = getRecordFactory().createHeader(nativeHeader);
697
      headers.add(header[0]);
698
      identifiers.add(header[1]);
699
      count++;
700
    }
701
702
    /* decide if you're done. */
703
    if (iterator.hasNext()) {
704
      resumptionId = getRSName();
705
      resumptionResults.put(resumptionId, iterator);
706
707
      /*****************************************************************
708
       * Construct the resumptionToken String however you see fit.
709
       *****************************************************************/
710
      StringBuffer resumptionTokenSb = new StringBuffer();
711
      resumptionTokenSb.append(resumptionId);
712
      resumptionTokenSb.append(":");
713
      resumptionTokenSb.append(Integer.toString(oldCount + count));
714
      resumptionTokenSb.append(":");
715
      resumptionTokenSb.append(Integer.toString(numRows));
716
      resumptionTokenSb.append(":");
717
      resumptionTokenSb.append(metadataPrefix);
718
719
      /*****************************************************************
720
       * Use the following line if you wish to include the optional
721
       * resumptionToken attributes in the response. Otherwise, use the line
722
       * after it that I've commented out.
723
       *****************************************************************/
724
      listIdentifiersMap.put("resumptionMap", getResumptionMap(
725
          resumptionTokenSb.toString(), numRows, oldCount));
726
      // listIdentifiersMap.put("resumptionMap",
727
      // getResumptionMap(resumptionTokenSb.toString()));
728
    }
729
730
    listIdentifiersMap.put("headers", headers.iterator());
731
    listIdentifiersMap.put("identifiers", identifiers.iterator());
732
    return listIdentifiersMap;
733
  }
734
735
736
  /**
737
   * Retrieve a list of records that satisfy the specified criteria
738
   *
739
   * @param from
740
   *          beginning date in the form of YYYY-MM-DD or null if earliest date
741
   *          is desired
742
   * @param until
743
   *          ending date in the form of YYYY-MM-DD or null if latest date is
744
   *          desired
745
   * @param set
746
   *          set name or null if no set is desired
747 4996 costa
   * @param metadataPrefix
748
   *          e.g. "oai_dc", "eml-2.0.1", "eml-2.1.0"
749
   *
750 4943 costa
   * @return a Map object containing an optional "resumptionToken" key/value
751
   *         pair and a "records" Iterator object. The "records" Iterator
752
   *         contains a set of Records objects.
753
   * @exception OAIBadRequestException
754
   *              signals an http status code 400 problem
755
   * @exception OAIInternalServerError
756
   *              signals an http status code 500 problem
757
   */
758
  public Map listRecords(String from, String until, String set,
759 4996 costa
                         String metadataPrefix)
760
      throws CannotDisseminateFormatException,
761
             OAIInternalServerError,
762
             NoItemsMatchException
763
  {
764 4943 costa
    purge(); // clean out old resumptionTokens
765 4946 costa
766
    Map<String, Object> listRecordsMap = new HashMap<String, Object>();
767
    ArrayList<String> records = new ArrayList<String>();
768
    filteredDateMap = filterDateMap(from, until, metadataPrefix);
769
    Iterator iterator = filteredDateMap.entrySet().iterator();
770
    int numRows = filteredDateMap.entrySet().size();
771 4943 costa
    int count = 0;
772
773
    while (count < maxListSize && iterator.hasNext()) {
774
      Map.Entry entryDateMap = (Map.Entry) iterator.next();
775
776 4946 costa
      try {
777
        String localIdentifier = (String) entryDateMap.getKey();
778
        HashMap<String, String> nativeItem =getMetacatDocument(localIdentifier);
779
        String record = constructRecord(nativeItem, metadataPrefix);
780
        records.add(record);
781
        count++;
782
      }
783
      catch (IOException e) {
784
        e.printStackTrace();
785
        throw new OAIInternalServerError(e.getMessage());
786 4943 costa
      }
787
    }
788
789 4946 costa
    if (count == 0) { throw new NoItemsMatchException(); }
790 4943 costa
791
    /* decide if you're done */
792
    if (iterator.hasNext()) {
793
      String resumptionId = getRSName();
794
      resumptionResults.put(resumptionId, iterator);
795
796
      /*****************************************************************
797
       * Construct the resumptionToken String however you see fit.
798
       *****************************************************************/
799
      StringBuffer resumptionTokenSb = new StringBuffer();
800
      resumptionTokenSb.append(resumptionId);
801
      resumptionTokenSb.append(":");
802
      resumptionTokenSb.append(Integer.toString(count));
803
      resumptionTokenSb.append(":");
804
      resumptionTokenSb.append(Integer.toString(numRows));
805
      resumptionTokenSb.append(":");
806
      resumptionTokenSb.append(metadataPrefix);
807
808
      /*****************************************************************
809
       * Use the following line if you wish to include the optional
810
       * resumptionToken attributes in the response. Otherwise, use the line
811
       * after it that I've commented out.
812
       *****************************************************************/
813 4946 costa
      listRecordsMap.put("resumptionMap",
814
                         getResumptionMap(resumptionTokenSb.toString(),
815
                                          numRows, 0
816
                                         )
817
                        );
818 4943 costa
      // listRecordsMap.put("resumptionMap",
819
      // getResumptionMap(resumptionTokenSb.toString()));
820
    }
821 4946 costa
822
    listRecordsMap.put("records", records.iterator());
823 4943 costa
    return listRecordsMap;
824
  }
825
826
827
  /**
828
   * Retrieve the next set of records associated with the resumptionToken
829
   *
830
   * @param resumptionToken
831
   *          implementation-dependent format taken from the previous
832
   *          listRecords() Map result.
833
   * @return a Map object containing an optional "resumptionToken" key/value
834
   *         pair and a "records" Iterator object. The "records" Iterator
835
   *         contains a set of Records objects.
836
   * @exception OAIBadRequestException
837
   *              signals an http status code 400 problem
838
   */
839
  public Map listRecords(String resumptionToken)
840
      throws BadResumptionTokenException {
841
    purge(); // clean out old resumptionTokens
842
    Map listRecordsMap = new HashMap();
843
    ArrayList records = new ArrayList();
844
845
    /**********************************************************************
846
     * parse your resumptionToken and look it up in the resumptionResults, if
847
     * necessary
848
     **********************************************************************/
849
    StringTokenizer tokenizer = new StringTokenizer(resumptionToken, ":");
850
    String resumptionId;
851
    int oldCount;
852
    String metadataPrefix;
853
    int numRows;
854
855
    try {
856
      resumptionId = tokenizer.nextToken();
857
      oldCount = Integer.parseInt(tokenizer.nextToken());
858
      numRows = Integer.parseInt(tokenizer.nextToken());
859
      metadataPrefix = tokenizer.nextToken();
860
    }
861
    catch (NoSuchElementException e) {
862
      throw new BadResumptionTokenException();
863
    }
864
865
    /* Get some more records from your database */
866
    Iterator iterator = (Iterator) resumptionResults.remove(resumptionId);
867
868
    if (iterator == null) {
869
      System.out
870
          .println("MetacatCatalog.listRecords(): reuse of old resumptionToken?");
871
      iterator = dateMap.entrySet().iterator();
872
      for (int i = 0; i < oldCount; ++i)
873
        iterator.next();
874
    }
875
876
    /* load the records ArrayLists. */
877
    int count = 0;
878
879
    while (count < maxListSize && iterator.hasNext()) {
880
      Map.Entry entryDateMap = (Map.Entry) iterator.next();
881
882
      try {
883
        String localIdentifier = (String) entryDateMap.getKey();
884
        HashMap nativeItem = getMetacatDocument(localIdentifier);
885
        String record = constructRecord(nativeItem, metadataPrefix);
886
        records.add(record);
887
        count++;
888
      }
889
      catch (CannotDisseminateFormatException e) {
890
        /* the client hacked the resumptionToken beyond repair */
891
        throw new BadResumptionTokenException();
892
      }
893
      catch (IOException e) {
894
        /* the file is probably missing */
895
        throw new BadResumptionTokenException();
896
      }
897
    }
898
899
    /* decide if you're done. */
900
    if (iterator.hasNext()) {
901
      resumptionId = getRSName();
902
      resumptionResults.put(resumptionId, iterator);
903
904
      /*****************************************************************
905
       * Construct the resumptionToken String however you see fit.
906
       *****************************************************************/
907
      StringBuffer resumptionTokenSb = new StringBuffer();
908
      resumptionTokenSb.append(resumptionId);
909
      resumptionTokenSb.append(":");
910
      resumptionTokenSb.append(Integer.toString(oldCount + count));
911
      resumptionTokenSb.append(":");
912
      resumptionTokenSb.append(Integer.toString(numRows));
913
      resumptionTokenSb.append(":");
914
      resumptionTokenSb.append(metadataPrefix);
915
916
      /*****************************************************************
917
       * Use the following line if you wish to include the optional
918
       * resumptionToken attributes in the response. Otherwise, use the line
919
       * after it that I've commented out.
920
       *****************************************************************/
921
      listRecordsMap.put("resumptionMap",
922
                         getResumptionMap(resumptionTokenSb.toString(),
923
                         numRows,
924
                         oldCount)
925
                        );
926
      // listRecordsMap.put("resumptionMap",
927
      // getResumptionMap(resumptionTokenSb.toString()));
928
    }
929
930
    listRecordsMap.put("records", records.iterator());
931
932
    return listRecordsMap;
933
  }
934
935
936
  public Map listSets() throws NoSetHierarchyException {
937
    throw new NoSetHierarchyException();
938
    // Map listSetsMap = new HashMap();
939
    // listSetsMap.put("sets", setsList.iterator());
940
    // return listSetsMap;
941
  }
942
943
944
  public Map listSets(String resumptionToken)
945
      throws BadResumptionTokenException {
946
    throw new BadResumptionTokenException();
947
  }
948
949
950
  /**
951 4946 costa
   * Run a query of the Metacat database to load the catalog of EML documents.
952
   * For each EML document, we store its 'docid', 'doctype', and 'date_updated'
953
   * values.
954
   */
955
  public void loadCatalog() {
956
    Statement stmt;
957
958
    try {
959
      Connection conn = getConnection();
960
961
      if (conn != null) {
962
        stmt = conn.createStatement();
963
        ResultSet rs = stmt.executeQuery(QUERY);
964
965
        int documentCount = 0;
966
967
        while (rs.next()) {
968
          documentCount++;
969
          String docid = rs.getString("docid");
970
          String doctype = rs.getString("doctype");
971
          String dateUpdated = rs.getDate("date_updated").toString();
972
          docTypeMap.put(docid, doctype);
973
          dateMap.put(docid, dateUpdated);
974
        }
975
976
        logger.info("Number of documents in catalog: " + documentCount);
977
978
        stmt.close();
979
        conn.close();
980
      }
981 4996 costa
982
      updateRefreshDate();
983 4946 costa
    }
984
    catch(SQLException e) {
985
      logger.error("SQLException: " + e.getMessage());
986
    }
987
  }
988
989
990
  /**
991 4943 costa
   * Purge tokens that are older than the time-to-live.
992
   */
993
  private void purge() {
994
    ArrayList old = new ArrayList();
995
    Date then, now = new Date();
996
    Iterator keySet = resumptionResults.keySet().iterator();
997
    String key;
998
999
    while (keySet.hasNext()) {
1000
      key = (String) keySet.next();
1001
      then = new Date(Long.parseLong(key) + getMillisecondsToLive());
1002
      if (now.after(then)) {
1003
        old.add(key);
1004
      }
1005
    }
1006
    Iterator iterator = old.iterator();
1007
    while (iterator.hasNext()) {
1008
      key = (String) iterator.next();
1009
      resumptionResults.remove(key);
1010
    }
1011
  }
1012
1013 4996 costa
1014
  /**
1015
   * Boolean to determine whether the catalog should be refreshed in memory.
1016
   *
1017
   * @return   true if the catalog should be refreshed, else false
1018
   */
1019
  private boolean shouldRefreshCatalog() {
1020
    boolean shouldRefresh = false;
1021
    String maxDateUpdated = getMaxDateUpdated();
1022
1023
    logger.info("refreshDate: " + refreshDate);
1024
    logger.info("maxDateUpdated: " + maxDateUpdated);
1025
1026
    /* If we don't know the last date that Metacat was updated or the last date
1027
     * the catalog was refreshed, then the catalog should be refreshed.
1028
     */
1029
    if ((refreshDate == null) || (maxDateUpdated == null)) {
1030
      shouldRefresh = true;
1031
    }
1032
    /* If the last date that Metacat was updated is greater than the last date
1033
     * the catalog was refreshed, then the catalog should be refreshed.
1034
     */
1035
    else if (maxDateUpdated.compareTo(refreshDate) > 0) {
1036
      shouldRefresh = true;
1037
    }
1038
1039
    logger.info("shouldRefresh: " + shouldRefresh);
1040
    return shouldRefresh;
1041
  }
1042
1043
1044
  /**
1045
   * Updates the refreshDate string to the current date.
1046
   */
1047
  private void updateRefreshDate() {
1048
    SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
1049
    Date now = new Date();
1050
    MetacatCatalog.refreshDate = simpleDateFormat.format(now);
1051
  }
1052
1053 4943 costa
}