ORG.oclc.oai.server.catalog.AbstractCatalog Maven / Gradle / Ivy
/**
* Copyright 2006 OCLC Online Computer Library Center Licensed under the Apache
* License, Version 2.0 (the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or
* agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and
* limitations under the License.
*/
package ORG.oclc.oai.server.catalog;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Properties;
import java.util.Vector;
import javax.servlet.ServletContext;
import ORG.oclc.oai.server.crosswalk.Crosswalks;
import ORG.oclc.oai.server.verb.BadArgumentException;
import ORG.oclc.oai.server.verb.BadResumptionTokenException;
import ORG.oclc.oai.server.verb.CannotDisseminateFormatException;
import ORG.oclc.oai.server.verb.IdDoesNotExistException;
import ORG.oclc.oai.server.verb.NoItemsMatchException;
import ORG.oclc.oai.server.verb.NoMetadataFormatsException;
import ORG.oclc.oai.server.verb.NoSetHierarchyException;
import ORG.oclc.oai.server.verb.OAIInternalServerError;
import ORG.oclc.oai.server.verb.ServerVerb;
/**
* AbstractCatalog is the generic interface between OAICat and any arbitrary
* database. Implement this interface to have OAICat work with your database.
*
* @author Jeffrey A. Young, OCLC Online Computer Library Center
*/
public abstract class AbstractCatalog {
private static final boolean debug = false;
/**
* The RecordFactory that understands how to convert this database's
* native "item" to the various metadataFormats to be supported.
*/
private RecordFactory recordFactory;
/**
* is this repository harvestable?
*/
private boolean harvestable = true;
/**
* optional property to limit the life of resumptionTokens (<0 indicates no limit)
**/
private int millisecondsToLive = -1;
/**
* Index into VALID_GRANULARITIES and FROM_GRANULARITIES
*/
private int supportedGranularityOffset = -1;
/**
* All possible valid granularities
*/
private static final String[] VALID_GRANULARITIES = {
"YYYY-MM-DD",
"YYYY-MM-DDThh:mm:ssZ"
};
/**
* minimum valid 'from' granularities
*/
private static final String[] FROM_GRANULARITIES = {
"0000-01-01",
"0000-01-01T00:00:00Z"
};
/**
* return a handle to the RecordFactory
* @return guess
*/
public RecordFactory getRecordFactory() { return recordFactory; }
public void setHarvestable(boolean harvestable) {
this.harvestable = harvestable;
}
/**
* Is this repository harvestable?
* @return true if harvestable, false otherwise.
*/
public boolean isHarvestable() { return harvestable; }
/**
* get the optional millisecondsToLive property (<0 indicates no limit)
**/
public int getMillisecondsToLive() { return millisecondsToLive; }
public void setRecordFactory(RecordFactory recordFactory) {
this.recordFactory = recordFactory;
}
public void setSupportedGranularityOffset(int i) {
supportedGranularityOffset = i;
}
/**
* Convert the requested 'from' parameter to the finest granularity supported
* by this repository.
* @exception BadArgumentException one or more of the arguments are bad.
*/
public String toFinestFrom(String from)
throws BadArgumentException {
if (debug) {
System.out.println("AbstractCatalog.toFinestFrom: from=" + from);
System.out.println(" target=" + VALID_GRANULARITIES[supportedGranularityOffset]);
}
if (from.length() > VALID_GRANULARITIES[supportedGranularityOffset].length()) {
throw new BadArgumentException();
}
if (from.length() != VALID_GRANULARITIES[supportedGranularityOffset].length()) {
StringBuffer sb = new StringBuffer(from);
if (sb.charAt(sb.length()-1) == 'Z')
sb.setLength(sb.length()-1);
sb.append(FROM_GRANULARITIES[supportedGranularityOffset].substring(sb.length()));
from = sb.toString();
}
if (!isValidGranularity(from)) {
throw new BadArgumentException();
}
return from;
}
/**
* Convert the requested 'until' paramter to the finest granularity supported
* by this repository
* @exception BadArgumentException one or more of the arguments are bad.
*/
public String toFinestUntil(String until)
throws BadArgumentException {
if (until.length() == VALID_GRANULARITIES[supportedGranularityOffset].length()) {
if (!isValidGranularity(until))
throw new BadArgumentException();
return until;
}
if (until.length() > VALID_GRANULARITIES[supportedGranularityOffset].length()) {
throw new BadArgumentException();
}
StringBuffer sb = new StringBuffer(until);
if (sb.charAt(sb.length()-1) == 'Z')
sb.setLength(sb.length()-1);
if (sb.length() < VALID_GRANULARITIES[0].length()) {
while (sb.length() < 4) sb.append("9");
switch (sb.length()) {
case 4: // YYYY
sb.append("-");
case 5: // YYYY-
sb.append("12");
case 7: // YYYY-MM
sb.append("-");
case 8: // YYYY-MM-
sb.append("31");
break;
case 6: // YYYY-M
case 9: // YYYY-MM-D
throw new BadArgumentException();
}
}
until = sb.toString();
if (until.length() == VALID_GRANULARITIES[supportedGranularityOffset].length()) {
if (!isValidGranularity(until))
throw new BadArgumentException();
return until;
}
if (sb.length() < VALID_GRANULARITIES[1].length()) {
switch (sb.length()) {
case 10: // YYYY-MM-DD
sb.append("T");
case 11: // YYYY-MM-DDT
sb.append("23");
case 13: // YYYY-MM-DDThh
sb.append(":");
case 14: // YYYY-MM-DDThh:
sb.append("59");
// case 16: // YYYY-MM-DDThh:mm
// sb.append("Z");
// break;
// case 12: // YYYY-MM-DDTh
// case 15: // YYYY-MM-DDThh:m
// throw new BadGranularityException();
// }
// }
// until = sb.toString();
// if (until.length() == VALID_GRANULARITIES[supportedGranularityOffset].length()) {
// if (!isValidGranularity(until))
// throw new BadGranularityException();
// return until;
// }
// if (sb.charAt(sb.length()-1) == 'Z')
// sb.setLength(sb.length()-1); // remove the trailing 'Z'
// if (sb.length() < VALID_GRANULARITIES[2].length()) {
// switch (sb.length()) {
case 16: // YYYY-MM-DDThh:mm
sb.append(":");
case 17: // YYYY-MM-DDThh:mm:
sb.append("59");
case 19: // YYYY-MM-DDThh:mm:ss
sb.append("Z");
break;
case 18: // YYYY-MM-DDThh:mm:s
throw new BadArgumentException();
}
}
// until = sb.toString();
// if (until.length() == VALID_GRANULARITIES[supportedGranularityOffset].length()) {
// if (!isValidGranularity(until))
// throw new BadGranularityException();
// return until;
// }
// if (sb.charAt(sb.length()-1) == 'Z')
// sb.setLength(sb.length()-1); // remove the trailing 'Z'
// switch (sb.length()) {
// case 19: // YYYY-MM-DDThh:mm:ss
// sb.append(".");
// case 20: // YYYY-MM-DDThh:mm:ss.
// sb.append("0");
// case 21: // YYYY-MM-DDThh:mm:ss.s
// sb.append("Z");
// break;
// }
until = sb.toString();
if (!isValidGranularity(until))
throw new BadArgumentException();
return until;
}
/**
* Does the specified date conform to the supported granularity of this repository?
* @param date a UTC date
* @return true if date conforms to the supported granularity of this repository,
* false otherwise.
*/
private boolean isValidGranularity(String date) {
if (date.length() > VALID_GRANULARITIES[supportedGranularityOffset].length())
return false;
if (date.length() < VALID_GRANULARITIES[0].length()
|| !Character.isDigit(date.charAt(0)) // YYYY
|| !Character.isDigit(date.charAt(1))
|| !Character.isDigit(date.charAt(2))
|| !Character.isDigit(date.charAt(3))
|| date.charAt(4) != '-'
|| !Character.isDigit(date.charAt(5)) // MM
|| !Character.isDigit(date.charAt(6))
|| date.charAt(7) != '-'
|| !Character.isDigit(date.charAt(8)) // DD
|| !Character.isDigit(date.charAt(9))) {
return false;
}
if (date.length() > VALID_GRANULARITIES[0].length()) {
if (date.charAt(10) != 'T'
|| date.charAt(date.length()-1) != 'Z'
|| !Character.isDigit(date.charAt(11)) // hh
|| !Character.isDigit(date.charAt(12))
|| date.charAt(13) != ':'
|| !Character.isDigit(date.charAt(14)) // mm
|| !Character.isDigit(date.charAt(15))
// ) {
// return false;
// }
// }
// if (date.length() > VALID_GRANULARITIES[1].length()) {
// if (
|| date.charAt(16) != ':'
|| !Character.isDigit(date.charAt(17)) // ss
|| !Character.isDigit(date.charAt(18))) {
return false;
}
}
// if (date.length() > VALID_GRANULARITIES[2].length()) {
// if (date.charAt(19) != '.'
// || !Character.isDigit(date.charAt(20))) { // s
// return false;
// }
// }
return true;
}
/**
* Retrieve the Crosswalks property
*
* @return the Crosswalks object containing a detailed list of oai
* formats supported by this application.
*/
public Crosswalks getCrosswalks() { return recordFactory.getCrosswalks(); }
/**
* Retrieve the list of supported Sets. This should probably be initialized
* by the constructor from the properties object that is passed to it.
*
* @return a Map object containing values as the Map keys and
* values for the corresponding the Map values.
* @exception NoSetHierarchyException No sets are defined for this repository
* @exception OAIInternalServerError An error occurred
*/
public abstract Map listSets() throws NoSetHierarchyException, OAIInternalServerError;
/**
* Retrieve the next cluster of supported sets.
* @return a Map object containing values as the Map keys and
* values for the corresponding the Map values.
* @exception BadResumptionTokenException The resumptionToken is bad.
* @exception OAIInternalServerError An error occurred
*/
public abstract Map listSets(String resumptionToken)
throws BadResumptionTokenException, OAIInternalServerError;
/**
* Factory method for creating an AbstractCatalog instance. The properties
* object must contain the following entries:
*
* - AbstractCatalog.className property which points to a class
* that implements the AbstractCatalog interface. Note that this class
* must have a constructor that accepts a properties object as a
* parameter.
* - Crosswalks.<supported formats> properties which
* satisfy the constructor for the Crosswalks class
*
*
* @param properties Properties object containing entries necessary to
* initialize the class
* to be created.
* @return on object instantiating the AbstractCatalog interface.
* @exception Throwable some sort of problem occurred.
*/
public static AbstractCatalog factory(Properties properties,
ServletContext context)
throws Throwable {
AbstractCatalog oaiCatalog = null;
String oaiCatalogClassName =
properties.getProperty("AbstractCatalog.oaiCatalogClassName");
String recordFactoryClassName =
properties.getProperty("AbstractCatalog.recordFactoryClassName");
if (oaiCatalogClassName == null) {
throw new ClassNotFoundException(
"AbstractCatalog.oaiCatalogClassName is missing from properties file");
}
if (recordFactoryClassName == null) {
throw new ClassNotFoundException(
"AbstractCatalog.recordFactoryClassName is missing from properties file");
}
Class oaiCatalogClass = Class.forName(oaiCatalogClassName);
try {
Constructor oaiCatalogConstructor = null;
try {
oaiCatalogConstructor =
oaiCatalogClass.getConstructor(new Class[] {Properties.class,
ServletContext.class});
oaiCatalog =
(AbstractCatalog)oaiCatalogConstructor.newInstance(new Object[]
{properties, context});
} catch (NoSuchMethodException e) {
oaiCatalogConstructor =
oaiCatalogClass.getConstructor(new Class[] {Properties.class});
oaiCatalog =
(AbstractCatalog)oaiCatalogConstructor.newInstance(new Object[]
{properties});
}
if (debug) {
System.out.println("AbstractCatalog.factory: recordFactoryClassName="
+ recordFactoryClassName);
}
Class recordFactoryClass = Class.forName(recordFactoryClassName);
Constructor recordFactoryConstructor =
recordFactoryClass.getConstructor(new Class[] {Properties.class});
oaiCatalog.recordFactory =
(RecordFactory)recordFactoryConstructor.newInstance(new Object[] {properties});
if (debug) {
System.out.println("AbstractCatalog.factory: recordFactory=" + oaiCatalog.recordFactory);
}
String harvestable = properties.getProperty("AbstractCatalog.harvestable");
if (harvestable != null && harvestable.equals("false")) {
oaiCatalog.harvestable = false;
}
String secondsToLive =
properties.getProperty("AbstractCatalog.secondsToLive");
if (secondsToLive != null) {
oaiCatalog.millisecondsToLive = Integer.parseInt(secondsToLive) * 1000;
}
String granularity = properties.getProperty("AbstractCatalog.granularity");
for (int i = 0; granularity != null && i < VALID_GRANULARITIES.length; ++i) {
if (granularity.equalsIgnoreCase(VALID_GRANULARITIES[i])) {
oaiCatalog.supportedGranularityOffset = i;
break;
}
}
if (oaiCatalog.supportedGranularityOffset == -1) {
oaiCatalog.supportedGranularityOffset = 0;
System.err.println("AbstractCatalog.factory: Invalid or missing AbstractCatalog.granularity property. Setting value to default: " +
VALID_GRANULARITIES[oaiCatalog.supportedGranularityOffset]);
}
} catch (InvocationTargetException e) {
throw e.getTargetException();
}
return oaiCatalog;
}
/**
* Allow the database to return some Identify <description> elements
*
* @return an XML String fragment containing description elements
*/
public String getDescriptions() {
return null;
}
/**
* Retrieve a list of schemaLocation values associated with the specified
* identifier.
*
* @param identifier the OAI identifier
* @return a Vector containing schemaLocation Strings
* @exception IdDoesNotExistException The specified identifier doesn't exist.
* @exception NoMetadataFormatsException The identifier exists, but no metadataFormats are
* provided for it.
* @exception OAIInternalServerError signals an http status code 500 problem
*/
public abstract Vector getSchemaLocations(String identifier)
throws IdDoesNotExistException, NoMetadataFormatsException, OAIInternalServerError;
/**
* Retrieve a list of Identifiers that satisfy the criteria parameters
*
* @param from beginning date in the form of YYYY-MM-DD or null if earliest
* date is desired
* @param until ending date in the form of YYYY-MM-DD or null if latest
* date is desired
* @param set set name or null if no set is desired
* @return a Map object containing an optional "resumptionToken" key/value
* pair and an "headers" Map object. The "headers" Map contains OAI
* identifier keys with corresponding values of "true" or null depending on
* whether the identifier is deleted or not.
* @exception BadArgumentException one or more of the arguments are bad.
* @exception CannotDisseminateFormatException the requested metadataPrefix isn't supported
* @exception NoItemsMatchException no items fit the criteria
* @exception NoSetHierarchyException sets aren't defined for this repository
* @exception OAIInternalServerError signals an http status code 500 problem
*/
public abstract Map listIdentifiers(String from, String until, String set, String metadataPrefix)
throws BadArgumentException, CannotDisseminateFormatException, NoItemsMatchException,
NoSetHierarchyException, OAIInternalServerError;
/**
* Retrieve the next set of Identifiers associated with the resumptionToken
*
* @param resumptionToken implementation-dependent format taken from the
* previous listIdentifiers() Map result.
* @return a Map object containing an optional "resumptionToken" key/value
* pair and an "headers" Map object. The "headers" Map contains OAI
* identifier keys with corresponding values of "true" or null depending on
* whether the identifier is deleted or not.
* @exception BadResumptionTokenException The resumptionToken is bad.
* @exception OAIInternalServerError signals an http status code 500 problem
*/
public abstract Map listIdentifiers(String resumptionToken)
throws BadResumptionTokenException, OAIInternalServerError;
/**
* Retrieve the specified metadata for the specified identifier
*
* @param identifier the OAI identifier
* @return the String containing the result record.
* @exception IdDoesNotExistException The specified identifier doesn't exist.
* @exception CannotDisseminateFormatException The identifier exists, but doesn't support
* the specified metadataPrefix.
* @exception OAIInternalServerError signals an http status code 500 problem
*/
public abstract String getRecord(String identifier, String metadataPrefix)
throws IdDoesNotExistException, CannotDisseminateFormatException, OAIInternalServerError;
/**
* Retrieve the specified metadata for the specified identifier
*
* @param identifier the OAI identifier
* @return the String containing the result record.
* @exception OAIInternalServerError signals an http status code 500 problem
* @throws CannotDisseminateFormatException
* @throws IdDoesNotExistException
* @throws IdDoesNotExistException
*/
public String getMetadata(String identifier, String metadataPrefix)
throws OAIInternalServerError, IdDoesNotExistException,
IdDoesNotExistException, CannotDisseminateFormatException {
throw new OAIInternalServerError("You need to override AbstractCatalog.getMetadata()");
}
/**
* Retrieve a list of records that satisfy the specified criteria
*
* @param from beginning date in the form of YYYY-MM-DD or null if earliest
* date is desired
* @param until ending date in the form of YYYY-MM-DD or null if latest
* date is desired
* @param set set name or null if no set is desired
* @return a Map object containing an optional "resumptionToken" key/value
* pair and a "records" Iterator object. The "records" Iterator contains a
* set of Records objects.
* @exception BadArgumentException one or more of the arguments are bad.
* @exception CannotDisseminateFormatException the requested metadataPrefix isn't supported
* @exception NoItemsMatchException no items fit the criteria
* @exception NoSetHierarchyException sets aren't defined for this repository
* @exception OAIInternalServerError signals an http status code 500 problem
*/
public Map listRecords(String from, String until, String set, String metadataPrefix)
throws BadArgumentException, CannotDisseminateFormatException, NoItemsMatchException,
NoSetHierarchyException, OAIInternalServerError {
if (debug) {
System.out.println("in AbstractCatalog.listRecords");
}
Map listIdentifiersMap = listIdentifiers(from, until, set, metadataPrefix);
String resumptionToken = (String)listIdentifiersMap.get("resumptionToken");
Iterator identifiers = (Iterator)listIdentifiersMap.get("identifiers");
Map listRecordsMap = new HashMap();
ArrayList records = new ArrayList();
while (identifiers.hasNext()) {
String identifier = (String)identifiers.next();
try {
records.add(getRecord(identifier, metadataPrefix));
} catch (IdDoesNotExistException e) {
throw new OAIInternalServerError("GetRecord failed to retrieve identifier '"
+ identifier + "'");
}
}
listRecordsMap.put("records", records.iterator());
if (resumptionToken != null) {
listRecordsMap.put("resumptionToken", resumptionToken);
}
return listRecordsMap;
}
/**
* Retrieve the next set of records associated with the resumptionToken
*
* @param resumptionToken implementation-dependent format taken from the
* previous listRecords() Map result.
* @return a Map object containing an optional "resumptionToken" key/value
* pair and a "records" Iterator object. The "records" Iterator contains a
* set of Records objects.
* @exception BadResumptionTokenException The resumptionToken is bad.
* @exception OAIInternalServerError signals an http status code 500 problem
*/
public Map listRecords(String resumptionToken)
throws BadResumptionTokenException, OAIInternalServerError {
Map listIdentifiersMap = listIdentifiers(resumptionToken);
resumptionToken = (String)listIdentifiersMap.get("resumptionToken");
Iterator identifiers = (Iterator)listIdentifiersMap.get("identifiers");
String metadataPrefix = (String)listIdentifiersMap.get("metadataPrefix");
Map listRecordsMap = new HashMap();
ArrayList records = new ArrayList();
while (identifiers.hasNext()) {
String identifier = (String)identifiers.next();
try {
records.add(getRecord(identifier, metadataPrefix));
} catch (IdDoesNotExistException e) {
throw new OAIInternalServerError("GetRecord failed to retrieve identifier '"
+ identifier + "'");
} catch (CannotDisseminateFormatException e) {
// someone cheated
throw new BadResumptionTokenException();
}
}
listRecordsMap.put("records", records.iterator());
if (resumptionToken != null) {
listRecordsMap.put("resumptionToken", resumptionToken);
}
return listRecordsMap;
}
public Map getResumptionMap(String resumptionToken) {
return getResumptionMap(resumptionToken, -1, -1);
}
public Map getResumptionMap(String resumptionToken, int completeListSize, int cursor) {
Map resumptionMap = null;
if (resumptionToken != null) {
resumptionMap = new HashMap();
resumptionMap.put("resumptionToken", resumptionToken);
if (millisecondsToLive > 0) {
// Date now = new Date();
Date then = new Date((new Date()).getTime() + millisecondsToLive);
resumptionMap.put("expirationDate", ServerVerb.createResponseDate(then));
}
if (completeListSize >= 0) {
resumptionMap.put("completeListSize", Integer.toString(completeListSize));
}
if (cursor >= 0) {
resumptionMap.put("cursor", Integer.toString(cursor));
}
}
return resumptionMap;
}
/**
* close the repository
*/
public abstract void close();
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy