ORG.oclc.oai.server.catalog.NewJDBCOAICatalog 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.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Properties;
import java.util.StringTokenizer;
import java.util.Vector;
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.util.OAIUtil;
/**
* NewJDBCOAICatalog is like JDBCOAICatalog except it is more intelligent
* about the coupling between Statement and ResultSet.
*
* @author Jeffrey A. Young, OCLC Online Computer Library Center
*/
public class NewJDBCOAICatalog extends AbstractCatalog {
/**
* The StatementResultSet inner class is used because Statement objects
* and ResultSet objects are tightly coupled and need to persist across
* multiple requests.
*
* @author Jeffrey A. Young
*/
private class StatementResultSet {
private Statement stmt = null;
private ResultSet rs = null;
public StatementResultSet(String query) throws SQLException {
Connection con = getConnection();
stmt = con.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY);
rs = stmt.executeQuery(query);
}
public Statement getStatement() { return stmt; }
public ResultSet getResultSet() { return rs; }
public void close() throws SQLException {
if (rs != null) {
rs.close();
rs = null;
}
if (stmt != null) {
stmt.close();
stmt = null;
}
}
/**
* Since the columns should only be read once, copy them into a
* HashMap and consider that to be the "record"
* @param rs The ResultSet row
* @return a HashMap mapping column names with values
*/
public HashMap getColumnValues()
throws SQLException {
ResultSetMetaData mdata = rs.getMetaData();
int count = mdata.getColumnCount();
HashMap nativeItem = new HashMap(count);
for (int i=1; i<=count; ++i) {
String fieldName = new StringBuffer().append(mdata.getTableName(i)).append(".").append(mdata.getColumnName(i)).toString();
nativeItem.put(fieldName, rs.getObject(i));
if (debug) System.out.println(fieldName + "=" + nativeItem.get(fieldName));
}
return nativeItem;
}
public boolean next() throws SQLException {
return rs.next();
}
public void last() throws SQLException {
rs.last();
}
public void beforeFirst() throws SQLException {
rs.beforeFirst();
}
public int getRow() throws SQLException {
return rs.getRow();
}
public boolean absolute(int oldCount) throws SQLException {
return rs.absolute(oldCount);
}
}
/**
* The JDBC Connection
*/
private Connection persistentConnection = null;
private static final boolean debug = false;
/**
* SQL identifier query (loaded from properties)
* \\i -> localIdentifier, \\o -> oaiIdentifier
*/
private String identifierQuery = null;
/**
* SQL range query (loaded from properties)
* \\f -> from, \\u -> until
*/
private String rangeQuery = null;
/**
* SQL range query (loaded from properties)
* \\f -> from, \\u -> until, \\s -> set
*/
private String rangeSetQuery = null;
/**
* SQL query to get a list of available sets
*/
private String setQuery = null;
/**
* SQL query to get a list of available sets that apply to a particular identifier
*/
private String setSpecQuery = null;
/**
* SQL query to get a list of available abouts that apply to a particular identifier
*/
private String aboutQuery = null;
/**
* SQL column labels containing the values of particular interest
*/
private String aboutValueLabel = null;
private String setSpecItemLabel = null;
private String setSpecListLabel = null;
private String setNameLabel = null;
private String setDescriptionLabel = null;
/**
* maximum number of entries to return for ListRecords and ListIdentifiers
* (loaded from properties)
*/
private int maxListSize;
/**
* The format required for dates in SQL queries
* "UTC" = YYYY-MM-DD or YYYY-MM-DDTHH:MM:SSZ
* other = YYYY/MM/DD
*/
private String dateFormat = null;
/**
* Set Strings to be loaded from the properties file
* (if they are to be loaded from properties rather than queried from the database)
*/
ArrayList sets = new ArrayList();
/**
* pending resumption tokens
*/
private HashMap resumptionResults = new HashMap();
private String jdbcURL = null;
private String jdbcLogin = null;
private String jdbcPasswd = null;
/**
* Construct a JDBCOAICatalog object
*
* @param properties a properties object containing initialization parameters
*/
public NewJDBCOAICatalog(Properties properties) {
dateFormat = properties.getProperty("JDBCOAICatalog.dateFormat");
String maxListSize = properties.getProperty("JDBCOAICatalog.maxListSize");
if (maxListSize == null) {
throw new IllegalArgumentException("JDBCOAICatalog.maxListSize is missing from the properties file");
} else {
this.maxListSize = Integer.parseInt(maxListSize);
}
String jdbcDriverName = properties.getProperty("JDBCOAICatalog.jdbcDriverName");
if (jdbcDriverName == null) {
throw new IllegalArgumentException("JDBCOAICatalog.jdbcDriverName is missing from the properties file");
}
jdbcURL = properties.getProperty("JDBCOAICatalog.jdbcURL");
if (jdbcURL == null) {
throw new IllegalArgumentException("JDBCOAICatalog.jdbcURL is missing from the properties file");
}
jdbcLogin = properties.getProperty("JDBCOAICatalog.jdbcLogin");
if (jdbcLogin == null) {
throw new IllegalArgumentException("JDBCOAICatalog.jdbcLogin is missing from the properties file");
}
jdbcPasswd = properties.getProperty("JDBCOAICatalog.jdbcPasswd");
if (jdbcPasswd == null) {
throw new IllegalArgumentException("JDBCOAICatalog.jdbcPasswd is missing from the properties file");
}
rangeQuery = properties.getProperty("JDBCOAICatalog.rangeQuery");
if (rangeQuery == null) {
throw new IllegalArgumentException("JDBCOAICatalog.rangeQuery is missing from the properties file");
}
rangeSetQuery = properties.getProperty("JDBCOAICatalog.rangeSetQuery");
if (rangeSetQuery == null) {
throw new IllegalArgumentException("JDBCOAICatalog.rangeSetQuery is missing from the properties file");
}
identifierQuery = properties.getProperty("JDBCOAICatalog.identifierQuery");
if (identifierQuery == null) {
throw new IllegalArgumentException("JDBCOAICatalog.identifierQuery is missing from the properties file");
}
aboutQuery = properties.getProperty("JDBCOAICatalog.aboutQuery");
if (aboutQuery != null) {
aboutValueLabel = properties.getProperty("JDBCOAICatalog.aboutValueLabel");
if (aboutValueLabel == null) {
throw new IllegalArgumentException("JDBCOAICatalog.aboutValueLabel is missing from the properties file");
}
}
setSpecQuery = properties.getProperty("JDBCOAICatalog.setSpecQuery");
setSpecItemLabel = properties.getProperty("JDBCOAICatalog.setSpecItemLabel");
if (setSpecItemLabel == null) {
throw new IllegalArgumentException("JDBCOAICatalog.setSpecItemLabel is missing from the properties file");
}
setSpecListLabel = properties.getProperty("JDBCOAICatalog.setSpecListLabel");
if (setSpecListLabel == null) {
throw new IllegalArgumentException("JDBCOAICatalog.setSpecListLabel is missing from the properties file");
}
setNameLabel = properties.getProperty("JDBCOAICatalog.setNameLabel");
if (setNameLabel == null) {
throw new IllegalArgumentException("JDBCOAICatalog.setNameLabel is missing from the properties file");
}
setDescriptionLabel = properties.getProperty("JDBCOAICatalog.setDescriptionLabel");
// See if a setQuery exists
setQuery = properties.getProperty("JDBCOAICatalog.setQuery");
if (setQuery == null) {
// if not, load the set Strings from the properties file (if present)
String propertyPrefix = "Sets.";
Enumeration propNames = properties.propertyNames();
while (propNames.hasMoreElements()) {
String propertyName = (String)propNames.nextElement();
if (propertyName.startsWith(propertyPrefix)) {
sets.add(properties.get(propertyName));
}
}
}
try {
Class.forName(jdbcDriverName);
} catch (ClassNotFoundException e) {
throw new IllegalArgumentException("JDBCOAICatalog.jdbcDriverName is invalid: "
+ jdbcDriverName);
}
}
/**
* Retrieve a list of schemaLocation values associated with the specified
* oaiIdentifier.
*
* @param oaiIdentifier the OAI identifier
* @return a Vector containing schemaLocation Strings
* @exception OAIInternalServerError signals an http status code 500 problem
* @exception IdDoesNotExistException the specified oaiIdentifier can't be found
* @exception NoMetadataFormatsException the specified oaiIdentifier was found
* but the item is flagged as deleted and thus no schemaLocations (i.e.
* metadataFormats) can be produced.
*/
public Vector getSchemaLocations(String oaiIdentifier)
throws OAIInternalServerError, IdDoesNotExistException, NoMetadataFormatsException {
StatementResultSet stmtRs = null;
try {
stmtRs = new StatementResultSet(populateIdentifierQuery(oaiIdentifier));
/*
* Let your recordFactory decide which schemaLocations
* (i.e. metadataFormats) it can produce from the record.
* Doing so will preserve the separation of database access
* (which happens here) from the record content interpretation
* (which is the responsibility of the RecordFactory implementation).
*/
if (!stmtRs.next()) {
throw new IdDoesNotExistException(oaiIdentifier);
} else {
/* Make sure the identifierQuery returns the columns you need
* (if any) to determine the supported schemaLocations for this item */
HashMap nativeItem = stmtRs.getColumnValues();
return getRecordFactory().getSchemaLocations(nativeItem);
}
} catch (SQLException e) {
e.printStackTrace();
throw new OAIInternalServerError(e.getMessage());
} finally {
try {
if (stmtRs != null)
stmtRs.close();
} catch (SQLException e) {
e.printStackTrace();
throw new OAIInternalServerError(e.getMessage());
}
}
}
/**
* insert actual from, until, and set parameters into the rangeQuery String
*
* @param from the OAI from parameter
* @param until the OAI until paramter
* @param set the OAI set parameter
* @return a String containing an SQL query
*/
private String populateRangeQuery(String from, String until, String set)
throws OAIInternalServerError {
StringBuffer sb = new StringBuffer();
StringTokenizer tokenizer;
if (set == null || set.length() == 0)
tokenizer = new StringTokenizer(rangeQuery, "\\");
else
tokenizer = new StringTokenizer(rangeSetQuery, "\\");
if (tokenizer.hasMoreTokens())
sb.append(tokenizer.nextToken());
else
throw new OAIInternalServerError("Invalid query");
while (tokenizer.hasMoreTokens()) {
String token = tokenizer.nextToken();
switch (token.charAt(0)) {
case 'f': // what are the chances someone would use this to indicate a form feed?
sb.append(formatFromDate(from));
break;
case 'u':
sb.append(formatUntilDate(until));
break;
case 's':
sb.append(set);
break;
default: // ignore it
sb.append("\\");
sb.append(token.charAt(0));
}
sb.append(token.substring(1));
}
if (debug) System.out.println(sb.toString());
return sb.toString();
}
/**
* Extend this class and override this method if necessary.
*/
protected String formatFromDate(String date) {
return formatDate(date);
}
/**
* Extend this class and override this method if necessary.
*/
protected String formatUntilDate(String date) {
return formatDate(date);
}
/**
* Change the String from UTC to SQL format
* If this method doesn't suit your needs, extend this class and override
* the method rather than change this code directly.
*/
protected String formatDate(String date) {
if ("UTC".equals(dateFormat)) {
return date;
} else {
StringBuffer sb = new StringBuffer();
sb.append(date.substring(5, 7));
sb.append("/");
sb.append(date.substring(8));
sb.append("/");
sb.append(date.substring(0, 4));
if (debug) System.out.println("JDBCOAICatalog.formatDate: from " + date + " to " + sb.toString());
return sb.toString();
}
}
/**
* insert actual from, until, and set parameters into the identifierQuery String
*
* @param from the OAI from parameter
* @param until the OAI until paramter
* @param set the OAI set parameter
* @return a String containing an SQL query
*/
private String populateIdentifierQuery(String oaiIdentifier)
throws OAIInternalServerError {
StringTokenizer tokenizer = new StringTokenizer(identifierQuery, "\\");
StringBuffer sb = new StringBuffer();
if (tokenizer.hasMoreTokens())
sb.append(tokenizer.nextToken());
else
throw new OAIInternalServerError("Invalid identifierQuery");
while (tokenizer.hasMoreTokens()) {
String token = tokenizer.nextToken();
switch (token.charAt(0)) {
case 'i':
sb.append(getRecordFactory().fromOAIIdentifier(oaiIdentifier));
break;
case 'o':
sb.append(oaiIdentifier);
break;
default: // ignore it
sb.append("\\");
sb.append(token.charAt(0));
}
sb.append(token.substring(1));
}
if (debug) System.out.println(sb.toString());
return sb.toString();
}
/**
* insert actual from, until, and set parameters into the identifierQuery String
*
* @param from the OAI from parameter
* @param until the OAI until paramter
* @param set the OAI set parameter
* @return a String containing an SQL query
*/
private String populateSetSpecQuery(String oaiIdentifier)
throws OAIInternalServerError {
StringTokenizer tokenizer = new StringTokenizer(setSpecQuery, "\\");
StringBuffer sb = new StringBuffer();
if (tokenizer.hasMoreTokens())
sb.append(tokenizer.nextToken());
else
throw new OAIInternalServerError("Invalid identifierQuery");
while (tokenizer.hasMoreTokens()) {
String token = tokenizer.nextToken();
switch (token.charAt(0)) {
case 'i':
sb.append(getRecordFactory().fromOAIIdentifier(oaiIdentifier));
break;
case 'o':
sb.append(oaiIdentifier);
break;
default: // ignore it
sb.append("\\");
sb.append(token.charAt(0));
}
sb.append(token.substring(1));
}
if (debug) System.out.println(sb.toString());
return sb.toString();
}
/**
* insert actual from, until, and set parameters into the identifierQuery String
*
* @param from the OAI from parameter
* @param until the OAI until paramter
* @param set the OAI set parameter
* @return a String containing an SQL query
*/
private String populateAboutQuery(String oaiIdentifier)
throws OAIInternalServerError {
StringTokenizer tokenizer = new StringTokenizer(aboutQuery, "\\");
StringBuffer sb = new StringBuffer();
if (tokenizer.hasMoreTokens())
sb.append(tokenizer.nextToken());
else
throw new OAIInternalServerError("Invalid identifierQuery");
while (tokenizer.hasMoreTokens()) {
String token = tokenizer.nextToken();
switch (token.charAt(0)) {
case 'i':
sb.append(getRecordFactory().fromOAIIdentifier(oaiIdentifier));
break;
case 'o':
sb.append(oaiIdentifier);
break;
default: // ignore it
sb.append("\\");
sb.append(token.charAt(0));
}
sb.append(token.substring(1));
}
if (debug) System.out.println(sb.toString());
return sb.toString();
}
/**
* Retrieve a list of identifiers that satisfy the specified criteria
*
* @param from beginning date using the proper granularity
* @param until ending date using the proper granularity
* @param set the set name or null if no such limit is requested
* @param metadataPrefix the OAI metadataPrefix or null if no such limit is requested
* @return a Map object containing entries for "headers" and "identifiers" Iterators
* (both containing Strings) as well as an optional "resumptionMap" Map.
* It may seem strange for the map to include both "headers" and "identifiers"
* since the identifiers can be obtained from the headers. This may be true, but
* AbstractCatalog.listRecords() can operate quicker if it doesn't
* need to parse identifiers from the XML headers itself. Better
* still, do like I do below and override AbstractCatalog.listRecords().
* AbstractCatalog.listRecords() is relatively inefficient because given the list
* of identifiers, it must call getRecord() individually for each as it constructs
* its response. It's much more efficient to construct the entire response in one fell
* swoop by overriding listRecords() as I've done here.
* @exception OAIInternalServerError signals an http status code 500 problem
*/
public Map listIdentifiers(String from, String until, String set, String metadataPrefix)
throws NoItemsMatchException,
OAIInternalServerError {
purge(); // clean out old resumptionTokens
Map listIdentifiersMap = new HashMap();
ArrayList headers = new ArrayList();
ArrayList identifiers = new ArrayList();
StatementResultSet stmtRs = null;
try {
/* Get some records from your database */
stmtRs = new StatementResultSet(populateRangeQuery(from, until, set));
stmtRs.last();
int numRows = stmtRs.getRow();
if (numRows == 0) {
throw new NoItemsMatchException();
}
stmtRs.beforeFirst();
int count;
/* load the headers and identifiers ArrayLists. */
for (count=0; count < maxListSize && stmtRs.next(); ++count) {
HashMap nativeItem = stmtRs.getColumnValues();
Iterator setSpecs = getSetSpecs(nativeItem);
String[] header = getRecordFactory().createHeader(nativeItem, setSpecs);
headers.add(header[0]);
identifiers.add(header[1]);
}
/* decide if you're done */
if (count < numRows) {
String resumptionId = getResumptionId();
resumptionResults.put(resumptionId, stmtRs);
/*****************************************************************
* Construct the resumptionToken String however you see fit.
*****************************************************************/
StringBuffer resumptionTokenSb = new StringBuffer();
resumptionTokenSb.append(resumptionId);
resumptionTokenSb.append("!");
resumptionTokenSb.append(Integer.toString(count));
resumptionTokenSb.append("!");
resumptionTokenSb.append(Integer.toString(numRows));
resumptionTokenSb.append("!");
resumptionTokenSb.append(metadataPrefix);
/*****************************************************************
* Use the following line if you wish to include the optional
* resumptionToken attributes in the response. Otherwise, use the
* line after it that I've commented out.
*****************************************************************/
listIdentifiersMap.put("resumptionMap", getResumptionMap(resumptionTokenSb.toString(),
numRows,
0));
} else {
stmtRs.close();
stmtRs = null;
}
} catch (SQLException e) {
if (stmtRs != null) {
try {
stmtRs.close();
} catch (SQLException e1) {
e1.printStackTrace();
}
}
e.printStackTrace();
throw new OAIInternalServerError(e.getMessage());
}
listIdentifiersMap.put("headers", headers.iterator());
listIdentifiersMap.put("identifiers", identifiers.iterator());
return listIdentifiersMap;
}
/**
* 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 entries for "headers" and "identifiers" Iterators
* (both containing Strings) as well as an optional "resumptionMap" Map.
* @exception BadResumptionTokenException the value of the resumptionToken
* is invalid or expired.
* @exception OAIInternalServerError signals an http status code 500 problem
*/
public Map listIdentifiers(String resumptionToken)
throws BadResumptionTokenException, OAIInternalServerError {
purge(); // clean out old resumptionTokens
Map listIdentifiersMap = new HashMap();
ArrayList headers = new ArrayList();
ArrayList identifiers = new ArrayList();
/**********************************************************************
* parse your resumptionToken and look it up in the resumptionResults,
* if necessary
**********************************************************************/
StringTokenizer tokenizer = new StringTokenizer(resumptionToken, "!");
String resumptionId;
int oldCount;
String metadataPrefix;
int numRows;
StatementResultSet stmtRs = null;
try {
resumptionId = tokenizer.nextToken();
oldCount = Integer.parseInt(tokenizer.nextToken());
numRows = Integer.parseInt(tokenizer.nextToken());
metadataPrefix = tokenizer.nextToken();
} catch (NoSuchElementException e) {
throw new BadResumptionTokenException();
}
try {
/* Get some more records from your database */
stmtRs = (StatementResultSet)resumptionResults.get(resumptionId);
if (stmtRs == null) {
throw new BadResumptionTokenException();
}
int count;
if (stmtRs.getRow() != oldCount) {
// System.out.println("JDBCOAICatalog.listIdentifiers: reuse of old resumptionToken?");
stmtRs.absolute(oldCount);
}
/* load the headers and identifiers ArrayLists. */
for (count = 0; count < maxListSize && stmtRs.next(); ++count) {
HashMap nativeItem = stmtRs.getColumnValues();
/* Use the RecordFactory to extract header/identifier pairs for each item */
Iterator setSpecs = getSetSpecs(nativeItem);
String[] header = getRecordFactory().createHeader(nativeItem, setSpecs);
headers.add(header[0]);
identifiers.add(header[1]);
}
/* decide if you're done. */
if (oldCount+count < numRows) {
/*****************************************************************
* Construct the resumptionToken String however you see fit.
*****************************************************************/
StringBuffer resumptionTokenSb = new StringBuffer();
resumptionTokenSb.append(resumptionId);
resumptionTokenSb.append("!");
resumptionTokenSb.append(Integer.toString(oldCount + count));
resumptionTokenSb.append("!");
resumptionTokenSb.append(Integer.toString(numRows));
resumptionTokenSb.append("!");
resumptionTokenSb.append(metadataPrefix);
/*****************************************************************
* Use the following line if you wish to include the optional
* resumptionToken attributes in the response. Otherwise, use the
* line after it that I've commented out.
*****************************************************************/
listIdentifiersMap.put("resumptionMap", getResumptionMap(resumptionTokenSb.toString(),
numRows,
oldCount));
} else {
stmtRs.close();
stmtRs = null;
}
} catch (SQLException e) {
if (stmtRs != null) {
try {
stmtRs.close();
} catch (SQLException e1) {
e1.printStackTrace();
}
}
e.printStackTrace();
throw new OAIInternalServerError(e.getMessage());
}
listIdentifiersMap.put("headers", headers.iterator());
listIdentifiersMap.put("identifiers", identifiers.iterator());
return listIdentifiersMap;
}
/**
* Retrieve the specified metadata for the specified oaiIdentifier
*
* @param oaiIdentifier the OAI identifier
* @param metadataPrefix the OAI metadataPrefix
* @return the portion of the XML response.
* @exception OAIInternalServerError signals an http status code 500 problem
* @exception CannotDisseminateFormatException the metadataPrefix is not
* supported by the item.
* @exception IdDoesNotExistException the oaiIdentifier wasn't found
*/
public String getRecord(String oaiIdentifier, String metadataPrefix)
throws OAIInternalServerError, CannotDisseminateFormatException,
IdDoesNotExistException {
StatementResultSet stmtRs = null;
try {
stmtRs = new StatementResultSet(populateIdentifierQuery(oaiIdentifier));
if (!stmtRs.next()) {
throw new IdDoesNotExistException(oaiIdentifier);
}
HashMap nativeItem = stmtRs.getColumnValues();
return constructRecord(nativeItem, metadataPrefix);
} catch (SQLException e) {
e.printStackTrace();
throw new OAIInternalServerError(e.getMessage());
} finally {
try {
if (stmtRs != null)
stmtRs.close();
} catch (SQLException e) {
e.printStackTrace();
throw new OAIInternalServerError(e.getMessage());
}
}
}
/**
* Retrieve a list of records that satisfy the specified criteria. Note, though,
* that unlike the other OAI verb type methods implemented here, both of the
* listRecords methods are already implemented in AbstractCatalog rather than
* abstracted. This is because it is possible to implement ListRecords as a
* combination of ListIdentifiers and GetRecord combinations. Nevertheless,
* I suggest that you override both the AbstractCatalog.listRecords methods
* here since it will probably improve the performance if you create the
* response in one fell swoop rather than construct it one GetRecord at a time.
*
* @param from beginning date using the proper granularity
* @param until ending date using the proper granularity
* @param set the set name or null if no such limit is requested
* @param metadataPrefix the OAI metadataPrefix or null if no such limit is requested
* @return a Map object containing entries for a "records" Iterator object
* (containing XML Strings) and an optional "resumptionMap" Map.
* @exception OAIInternalServerError signals an http status code 500 problem
* @exception CannotDisseminateFormatException the metadataPrefix isn't
* supported by the item.
*/
public Map listRecords(String from, String until, String set, String metadataPrefix)
throws CannotDisseminateFormatException, NoItemsMatchException,
OAIInternalServerError {
purge(); // clean out old resumptionTokens
Map listRecordsMap = new HashMap();
ArrayList records = new ArrayList();
StatementResultSet stmtRs = null;
try {
stmtRs = new StatementResultSet(populateRangeQuery(from, until, set));
stmtRs.last();
int numRows = stmtRs.getRow();
if (numRows == 0) {
throw new NoItemsMatchException();
}
stmtRs.beforeFirst();
int count;
// if (debug) System.out.println("JDBCOAICatalog.listRecords: numRows=" + numRows);
/* load the records ArrayList */
for (count=0; count < maxListSize && stmtRs.next(); ++count) {
HashMap nativeItem = stmtRs.getColumnValues();
String record = constructRecord(nativeItem, metadataPrefix);
records.add(record);
}
/* decide if you're done */
if (count < numRows) {
String resumptionId = getResumptionId();
resumptionResults.put(resumptionId, stmtRs);
/*****************************************************************
* Construct the resumptionToken String however you see fit.
*****************************************************************/
StringBuffer resumptionTokenSb = new StringBuffer();
resumptionTokenSb.append(resumptionId);
resumptionTokenSb.append("!");
resumptionTokenSb.append(Integer.toString(count));
resumptionTokenSb.append("!");
resumptionTokenSb.append(Integer.toString(numRows));
resumptionTokenSb.append("!");
resumptionTokenSb.append(metadataPrefix);
/*****************************************************************
* Use the following line if you wish to include the optional
* resumptionToken attributes in the response. Otherwise, use the
* line after it that I've commented out.
*****************************************************************/
listRecordsMap.put("resumptionMap", getResumptionMap(resumptionTokenSb.toString(),
numRows,
0));
// listRecordsMap.put("resumptionMap",
// getResumptionMap(resumptionTokenSbSb.toString()));
} else {
stmtRs.close();
stmtRs = null;
}
} catch (SQLException e) {
if (stmtRs != null) {
try {
stmtRs.close();
} catch (SQLException e1) {
e1.printStackTrace();
}
}
e.printStackTrace();
throw new OAIInternalServerError(e.getMessage());
}
listRecordsMap.put("records", records.iterator());
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 entries for "headers" and "identifiers" Iterators
* (both containing Strings) as well as an optional "resumptionMap" Map.
* @exception OAIInternalServerError signals an http status code 500 problem
* @exception BadResumptionTokenException the value of the resumptionToken argument
* is invalid or expired.
*/
public Map listRecords(String resumptionToken)
throws BadResumptionTokenException, OAIInternalServerError {
Map listRecordsMap = new HashMap();
ArrayList records = new ArrayList();
purge(); // clean out old resumptionTokens
/**********************************************************************
* parse your resumptionToken and look it up in the resumptionResults,
* if necessary
**********************************************************************/
StringTokenizer tokenizer = new StringTokenizer(resumptionToken, "!");
String resumptionId;
int oldCount;
int numRows;
String metadataPrefix;
StatementResultSet stmtRs = null;
try {
resumptionId = tokenizer.nextToken();
oldCount = Integer.parseInt(tokenizer.nextToken());
numRows = Integer.parseInt(tokenizer.nextToken());
metadataPrefix = tokenizer.nextToken();
} catch (NoSuchElementException e) {
throw new BadResumptionTokenException();
}
try {
/* Get some more records from your database */
stmtRs = (StatementResultSet) resumptionResults.get(resumptionId);
if (stmtRs == null) {
throw new BadResumptionTokenException();
}
if (stmtRs.getRow() != oldCount) {
// System.out.println("JDBCOAICatalog.listIdentifiers: reuse of old resumptionToken?");
stmtRs.absolute(oldCount);
}
int count;
/* load the headers and identifiers ArrayLists. */
for (count = 0; count < maxListSize && stmtRs.next(); ++count) {
try {
HashMap nativeItem = stmtRs.getColumnValues();
String record = constructRecord(nativeItem, metadataPrefix);
records.add(record);
} catch (CannotDisseminateFormatException e) {
/* the client hacked the resumptionToken beyond repair */
throw new BadResumptionTokenException();
}
}
/* decide if you're done */
if (oldCount+count < numRows) {
/*****************************************************************
* Construct the resumptionToken String however you see fit.
*****************************************************************/
StringBuffer resumptionTokenSb = new StringBuffer();
resumptionTokenSb.append(resumptionId);
resumptionTokenSb.append("!");
resumptionTokenSb.append(Integer.toString(oldCount + count));
resumptionTokenSb.append("!");
resumptionTokenSb.append(Integer.toString(numRows));
resumptionTokenSb.append("!");
resumptionTokenSb.append(metadataPrefix);
/*****************************************************************
* Use the following line if you wish to include the optional
* resumptionToken attributes in the response. Otherwise, use the
* line after it that I've commented out.
*****************************************************************/
listRecordsMap.put("resumptionMap", getResumptionMap(resumptionTokenSb.toString(),
numRows,
oldCount));
// listRecordsMap.put("resumptionMap",
// getResumptionMap(resumptionTokenSb.toString()));
} else {
stmtRs.close();
stmtRs = null;
}
} catch (SQLException e) {
if (stmtRs != null) {
try {
stmtRs.close();
} catch (SQLException e1) {
e1.printStackTrace();
}
}
e.printStackTrace();
throw new OAIInternalServerError(e.getMessage());
}
listRecordsMap.put("records", records.iterator());
return listRecordsMap;
}
/**
* Utility method to construct a Record object for a specified
* metadataFormat from a native record
*
* @param nativeItem native item from the dataase
* @param metadataPrefix the desired metadataPrefix for performing the crosswalk
* @return the String
* @exception CannotDisseminateFormatException the record is not available
* for the specified metadataPrefix.
*/
private String constructRecord(HashMap nativeItem, String metadataPrefix)
throws CannotDisseminateFormatException, OAIInternalServerError {
String schemaURL = null;
Iterator setSpecs = getSetSpecs(nativeItem);
Iterator abouts = getAbouts(nativeItem);
if (metadataPrefix != null) {
if ((schemaURL = getCrosswalks().getSchemaURL(metadataPrefix)) == null)
throw new CannotDisseminateFormatException(metadataPrefix);
}
return getRecordFactory().create(nativeItem, schemaURL, metadataPrefix, setSpecs, abouts);
}
/**
* Retrieve a list of sets that satisfy the specified criteria
*
* @return a Map object containing "sets" Iterator object (contains
* XML Strings) as well as an optional resumptionMap Map.
* @exception OAIBadRequestException signals an http status code 400 problem
* @exception OAIInternalServerError signals an http status code 500 problem
*/
public Map listSets() throws NoSetHierarchyException, OAIInternalServerError {
StatementResultSet stmtRs = null;
if (setQuery == null) {
if (sets.size() == 0)
throw new NoSetHierarchyException();
Map listSetsMap = new HashMap();
listSetsMap.put("sets", sets.iterator());
return listSetsMap;
} else {
purge(); // clean out old resumptionTokens
Map listSetsMap = new HashMap();
ArrayList sets = new ArrayList();
try {
if (debug) System.out.println(setQuery);
/* Get some records from your database */
stmtRs = new StatementResultSet(setQuery);
stmtRs.last();
int numRows = stmtRs.getRow();
stmtRs.beforeFirst();
int count;
// if (debug) System.out.println("JDBCOAICatalog.listSets: numRows=" + numRows);
/* load the sets ArrayLists. */
for (count=0; count < maxListSize && stmtRs.next(); ++count) {
/* Use the RecordFactory to extract header/set pairs for each item */
HashMap nativeItem = stmtRs.getColumnValues();
sets.add(getSetXML(nativeItem));
if (debug) System.out.println("JDBCOAICatalog.listSets: adding an entry");
}
/* decide if you're done */
if (count < numRows) {
String resumptionId = getResumptionId();
/*****************************************************************
* Note that storing the ResultSet in the resumptionResult
* means the token can't be reused.
*****************************************************************/
resumptionResults.put(resumptionId, stmtRs);
/*****************************************************************
* Construct the resumptionToken String however you see fit.
*****************************************************************/
StringBuffer resumptionTokenSb = new StringBuffer();
resumptionTokenSb.append(resumptionId);
resumptionTokenSb.append("!");
resumptionTokenSb.append(Integer.toString(count));
resumptionTokenSb.append("!");
resumptionTokenSb.append(Integer.toString(numRows));
/*****************************************************************
* Use the following line if you wish to include the optional
* resumptionToken attributes in the response. Otherwise, use the
* line after it that I've commented out.
*****************************************************************/
listSetsMap.put("resumptionMap", getResumptionMap(resumptionTokenSb.toString(),
numRows,
0));
} else {
stmtRs.close();
stmtRs = null;
}
} catch (SQLException e) {
if (stmtRs != null) {
try {
stmtRs.close();
} catch (SQLException e1) {
e1.printStackTrace();
}
}
e.printStackTrace();
throw new OAIInternalServerError(e.getMessage());
}
listSetsMap.put("sets", sets.iterator());
return listSetsMap;
}
}
/**
* Retrieve the next set of sets associated with the resumptionToken
*
* @param resumptionToken implementation-dependent format taken from the
* previous listSets() Map result.
* @return a Map object containing "sets" Iterator object (contains
* XML Strings) as well as an optional resumptionMap Map.
* @exception BadResumptionTokenException the value of the resumptionToken
* is invalid or expired.
* @exception OAIInternalServerError signals an http status code 500 problem
*/
public Map listSets(String resumptionToken)
throws OAIInternalServerError, BadResumptionTokenException {
StatementResultSet stmtRs = null;
if (setQuery == null) {
throw new BadResumptionTokenException();
} else {
purge(); // clean out old resumptionTokens
Map listSetsMap = new HashMap();
ArrayList sets = new ArrayList();
/**********************************************************************
* parse your resumptionToken and look it up in the resumptionResults,
* if necessary
**********************************************************************/
StringTokenizer tokenizer = new StringTokenizer(resumptionToken, "!");
String resumptionId;
int oldCount;
int numRows;
try {
resumptionId = tokenizer.nextToken();
oldCount = Integer.parseInt(tokenizer.nextToken());
numRows = Integer.parseInt(tokenizer.nextToken());
} catch (NoSuchElementException e) {
throw new BadResumptionTokenException();
}
try {
/* Get some more records from your database */
stmtRs = (StatementResultSet)resumptionResults.get(resumptionId);
if (stmtRs == null) {
throw new BadResumptionTokenException();
}
if (stmtRs.getRow() != oldCount) {
// System.out.println("JDBCOAICatalog.listIdentifiers: reuse of old resumptionToken?");
stmtRs.absolute(oldCount);
}
int count;
/* load the sets ArrayLists. */
for (count = 0; count < maxListSize && stmtRs.next(); ++count) {
HashMap nativeItem = stmtRs.getColumnValues();
/* Use the RecordFactory to extract set for each item */
sets.add(getSetXML(nativeItem));
}
/* decide if you're done. */
if (oldCount+count < numRows) {
/*****************************************************************
* Construct the resumptionToken String however you see fit.
*****************************************************************/
StringBuffer resumptionTokenSb = new StringBuffer();
resumptionTokenSb.append(resumptionId);
resumptionTokenSb.append("!");
resumptionTokenSb.append(Integer.toString(oldCount + count));
resumptionTokenSb.append("!");
resumptionTokenSb.append(Integer.toString(numRows));
/*****************************************************************
* Use the following line if you wish to include the optional
* resumptionToken attributes in the response. Otherwise, use the
* line after it that I've commented out.
*****************************************************************/
listSetsMap.put("resumptionMap", getResumptionMap(resumptionTokenSb.toString(),
numRows,
oldCount));
} else {
stmtRs.close();
stmtRs = null;
}
} catch (SQLException e) {
if (stmtRs != null) {
try {
stmtRs.close();
} catch (SQLException e1) {
e1.printStackTrace();
}
}
e.printStackTrace();
throw new OAIInternalServerError(e.getMessage());
}
listSetsMap.put("sets", sets.iterator());
return listSetsMap;
}
}
/**
* get an Iterator containing the setSpecs for the nativeItem
*
* @param rs ResultSet containing the nativeItem
* @return an Iterator containing the list of setSpec values for this nativeItem
*/
private Iterator getSetSpecs(HashMap nativeItem)
throws OAIInternalServerError {
StatementResultSet stmtRs = null;
try {
ArrayList setSpecs = new ArrayList();
if (setSpecQuery != null) {
RecordFactory rf = getRecordFactory();
String oaiIdentifier = rf.getOAIIdentifier(nativeItem);
stmtRs = new StatementResultSet(populateSetSpecQuery(oaiIdentifier));
while (stmtRs.next()) {
HashMap setMap = stmtRs.getColumnValues();
setSpecs.add(setMap.get(setSpecItemLabel).toString());
}
}
return setSpecs.iterator();
} catch (SQLException e) {
e.printStackTrace();
throw new OAIInternalServerError(e.getMessage());
} finally {
try {
stmtRs.close();
} catch (SQLException e) {
e.printStackTrace();
throw new OAIInternalServerError(e.getMessage());
}
}
}
/**
* get an Iterator containing the abouts for the nativeItem
*
* @param rs ResultSet containing the nativeItem
* @return an Iterator containing the list of about values for this nativeItem
*/
private Iterator getAbouts(HashMap nativeItem)
throws OAIInternalServerError {
StatementResultSet stmtRs = null;
try {
ArrayList abouts = new ArrayList();
if (aboutQuery != null) {
RecordFactory rf = getRecordFactory();
String oaiIdentifier = rf.getOAIIdentifier(nativeItem);
stmtRs = new StatementResultSet(populateAboutQuery(oaiIdentifier));
while (stmtRs.next()) {
HashMap aboutMap = stmtRs.getColumnValues();
abouts.add(aboutMap.get(aboutValueLabel));
}
}
return abouts.iterator();
} catch (SQLException e) {
e.printStackTrace();
throw new OAIInternalServerError(e.getMessage());
} finally {
try {
stmtRs.close();
} catch (SQLException e) {
e.printStackTrace();
throw new OAIInternalServerError(e.getMessage());
}
}
}
/**
* Extract <set> XML string from setItem object
*
* @param setItem individual set instance in native format
* @return an XML String containing the XML <set> content
*/
public String getSetXML(HashMap setItem)
throws IllegalArgumentException {
String setSpec = getSetSpec(setItem);
String setName = getSetName(setItem);
String setDescription = getSetDescription(setItem);
StringBuffer sb = new StringBuffer();
sb.append("");
sb.append("");
sb.append(OAIUtil.xmlEncode(setSpec));
sb.append(" ");
sb.append("");
sb.append(OAIUtil.xmlEncode(setName));
sb.append(" ");
if (setDescription != null) {
sb.append("");
sb.append(OAIUtil.xmlEncode(setDescription));
sb.append(" ");
}
sb.append(" ");
return sb.toString();
}
/**
* get the setSpec XML string. Extend this class and override this method
* if the setSpec can't be directly taken from the result set as a String
*
* @param rs ResultSet
* @return an XML String containing the <setSpec> content
*/
protected String getSetSpec(HashMap setItem) {
try {
return URLEncoder.encode((String)setItem.get(setSpecListLabel), "UTF-8");
} catch (UnsupportedEncodingException e) {
return "UnsupportedEncodingException";
}
}
/**
* get the setName XML string. Extend this class and override this method
* if the setName can't be directly taken from the result set as a String
*
* @param rs ResultSet
* @return an XML String containing the <setName> content
*/
protected String getSetName(HashMap setItem) {
return (String)setItem.get(setNameLabel);
}
/**
* get the setDescription XML string. Extend this class and override this method
* if the setDescription can't be directly taken from the result set as a String
*
* @param rs ResultSet
* @return an XML String containing the <setDescription> content
*/
protected String getSetDescription(HashMap setItem) {
if (setDescriptionLabel == null)
return null;
return (String)setItem.get(setDescriptionLabel);
}
private Connection getNewConnection() throws SQLException {
// open the connection
return DriverManager.getConnection(jdbcURL, jdbcLogin, jdbcPasswd);
}
private Connection getConnection() throws SQLException {
if (persistentConnection != null) {
if (persistentConnection.isClosed()) {
System.out.println("Persistent connection has expired.");
persistentConnection = getNewConnection();
}
} else {
persistentConnection = getNewConnection();
}
return persistentConnection;
}
/**
* close the repository
*/
public void close() {
}
/**
* Purge tokens that are older than the configured time-to-live.
*/
private void purge() {
ArrayList old = new ArrayList();
Date now = new Date();
Iterator keySet = resumptionResults.keySet().iterator();
while (keySet.hasNext()) {
String key = (String)keySet.next();
Date then = new Date(Long.parseLong(key) + getMillisecondsToLive());
if (now.after(then)) {
old.add(key);
}
}
Iterator iterator = old.iterator();
while (iterator.hasNext()) {
String key = (String)iterator.next();
resumptionResults.remove(key);
}
}
/**
* Use the current date as the basis for the resumptiontoken
*
* @return a String version of the current time
*/
private synchronized static String getResumptionId() {
Date now = new Date();
return Long.toString(now.getTime());
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy