ORG.oclc.oai.server.catalog.ExtendedJDBCOAICatalog 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.IOException;
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;
/**
* ExtendedJDBCOAICatalog is an example of how to implement the AbstractCatalog interface
* for JDBC. Pattern an implementation of the AbstractCatalog interface after this class
* to have OAICat work with your JDBC database.
*
* @author Jeffrey A. Young, OCLC Online Computer Library Center
*/
public class ExtendedJDBCOAICatalog extends AbstractCatalog {
private static final boolean debug = true;
/**
* 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;
/**
* 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();
/**
* The JDBC Connection
*/
private boolean isPersistentConnection = true;
private Connection persistentConnection;
private String jdbcURL = null;
private String jdbcLogin = null;
private String jdbcPasswd = null;
/**
* pending resumption tokens
*/
private HashMap resumptionResults = new HashMap();
/**
* Construct a ExtendedJDBCOAICatalog object
*
* @param properties a properties object containing initialization parameters
* @exception IOException an I/O error occurred during database initialization.
*/
public ExtendedJDBCOAICatalog(Properties properties) throws IOException {
String maxListSize = properties.getProperty("ExtendedJDBCOAICatalog.maxListSize");
if (maxListSize == null) {
throw new IllegalArgumentException("ExtendedJDBCOAICatalog.maxListSize is missing from the properties file");
} else {
this.maxListSize = Integer.parseInt(maxListSize);
}
String jdbcDriverName = properties.getProperty("ExtendedJDBCOAICatalog.jdbcDriverName");
if (jdbcDriverName == null) {
throw new IllegalArgumentException("ExtendedJDBCOAICatalog.jdbcDriverName is missing from the properties file");
}
jdbcURL = properties.getProperty("ExtendedJDBCOAICatalog.jdbcURL");
if (jdbcURL == null) {
throw new IllegalArgumentException("ExtendedJDBCOAICatalog.jdbcURL is missing from the properties file");
}
jdbcLogin = properties.getProperty("ExtendedJDBCOAICatalog.jdbcLogin");
if (jdbcLogin == null) {
throw new IllegalArgumentException("ExtendedJDBCOAICatalog.jdbcLogin is missing from the properties file");
}
jdbcPasswd = properties.getProperty("ExtendedJDBCOAICatalog.jdbcPasswd");
if (jdbcPasswd == null) {
throw new IllegalArgumentException("ExtendedJDBCOAICatalog.jdbcPasswd is missing from the properties file");
}
rangeQuery = properties.getProperty("ExtendedJDBCOAICatalog.rangeQuery");
if (rangeQuery == null) {
throw new IllegalArgumentException("ExtendedJDBCOAICatalog.rangeQuery is missing from the properties file");
}
rangeSetQuery = properties.getProperty("ExtendedJDBCOAICatalog.rangeSetQuery");
if (rangeSetQuery == null) {
throw new IllegalArgumentException("ExtendedJDBCOAICatalog.rangeSetQuery is missing from the properties file");
}
identifierQuery = properties.getProperty("ExtendedJDBCOAICatalog.identifierQuery");
if (identifierQuery == null) {
throw new IllegalArgumentException("ExtendedJDBCOAICatalog.identifierQuery is missing from the properties file");
}
aboutQuery = properties.getProperty("ExtendedJDBCOAICatalog.aboutQuery");
if (aboutQuery != null) {
aboutValueLabel = properties.getProperty("ExtendedJDBCOAICatalog.aboutValueLabel");
if (aboutValueLabel == null) {
throw new IllegalArgumentException("ExtendedJDBCOAICatalog.aboutValueLabel is missing from the properties file");
}
}
setSpecQuery = properties.getProperty("ExtendedJDBCOAICatalog.setSpecQuery");
setSpecItemLabel = properties.getProperty("ExtendedJDBCOAICatalog.setSpecItemLabel");
if (setSpecItemLabel == null) {
throw new IllegalArgumentException("ExtendedJDBCOAICatalog.setSpecItemLabel is missing from the properties file");
}
setSpecListLabel = properties.getProperty("ExtendedJDBCOAICatalog.setSpecListLabel");
if (setSpecListLabel == null) {
throw new IllegalArgumentException("ExtendedJDBCOAICatalog.setSpecListLabel is missing from the properties file");
}
setNameLabel = properties.getProperty("ExtendedJDBCOAICatalog.setNameLabel");
if (setNameLabel == null) {
throw new IllegalArgumentException("ExtendedJDBCOAICatalog.setNameLabel is missing from the properties file");
}
setDescriptionLabel = properties.getProperty("ExtendedJDBCOAICatalog.setDescriptionLabel");
// if (setDescriptionLabel == null) {
// throw new IllegalArgumentException("ExtendedJDBCOAICatalog.setDescriptionLabel is missing from the properties file");
// }
// See if a setQuery exists
setQuery = properties.getProperty("ExtendedJDBCOAICatalog.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));
}
}
}
String temp = properties.getProperty("ExtendedJDBCOAICatalog.isPersistentConnection");
if ("false".equalsIgnoreCase(temp))
isPersistentConnection = false;
// open the connection
try {
Class.forName(jdbcDriverName);
} catch (ClassNotFoundException e) {
throw new IllegalArgumentException("ExtendedJDBCOAICatalog.jdbcDriverName is invalid: "
+ jdbcDriverName);
}
if (isPersistentConnection) {
try {
persistentConnection = getNewConnection();
} catch (SQLException e) {
e.printStackTrace();
throw new IOException(e.getMessage());
}
}
}
private Connection getNewConnection() throws SQLException {
// open the connection
return DriverManager.getConnection(jdbcURL, jdbcLogin, jdbcPasswd);
}
private Connection startConnection() throws SQLException {
if (persistentConnection != null) {
if (persistentConnection.isClosed())
persistentConnection = getNewConnection();
return persistentConnection;
} else {
return getNewConnection();
}
}
private void endConnection(Connection con) throws OAIInternalServerError {
try {
if (persistentConnection == null)
con.close();
} catch (SQLException e) {
throw new OAIInternalServerError(e.getMessage());
}
}
/**
* 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 {
Connection con = null;
try {
con = startConnection();
Statement stmt = con.createStatement();
ResultSet rs =
stmt.executeQuery(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 (!rs.next()) {
endConnection(con);
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 = new HashMap();
nativeItem.put("coreResult", getColumnValues(rs));
endConnection(con);
return getRecordFactory().getSchemaLocations(nativeItem);
}
} catch (SQLException e) {
if (con != null)
endConnection(con);
e.printStackTrace();
throw new OAIInternalServerError(e.getMessage());
}
}
/**
* Get remainder of the nativeItem. By default, it is assumed
* the entire nativeItem is produced by the default query.
* If other tables must be consulted, extend this class and
* override this method. For best results, create one new entry
* in the nativeItem HashMap for each additional query peformed.
*/
protected void extendItem(Connection con, HashMap nativeItem) {
}
/**
* 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
*/
private HashMap getColumnValues(ResultSet rs)
throws SQLException {
ResultSetMetaData mdata = rs.getMetaData();
int count = mdata.getColumnCount();
HashMap tableItems = new HashMap(count);
for (int i=1; i<=count; ++i) {
String fieldName = new StringBuffer().append(mdata.getTableName(i)).append(".").append(mdata.getColumnName(i)).toString();
tableItems.put(fieldName, rs.getObject(i));
if (debug) System.out.println(fieldName + "=" + tableItems.get(fieldName));
// if (debug) {
// System.out.println("ExtendedJDBCOAICatalog.getColumnValues");
// System.out.println("catalogName=" + mdata.getCatalogName(i));
// System.out.println("columnLabel=" + mdata.getColumnLabel(i));
// System.out.println("columnName=" + mdata.getColumnName(i));
// System.out.println("schemaName=" + mdata.getSchemaName(i));
// System.out.println("tableName=" + mdata.getTableName(i));
// }
}
return tableItems;
}
/**
* insert actual from, until, and set parameters into the rangeQuery String
* NOTE! This retrieves an extra record so we can decide if EOF has been reached.
*
* @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, int offset, int count)
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 'a':
sb.append(Integer.toString(offset));
break;
case 'b':
// sb.append(Integer.toString(count));
sb.append(Integer.toString(count+1)); // grab an extra record to decide if EOF
break;
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) {
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("ExtendedJDBCOAICatalog.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();
Connection con = null;
try {
con = startConnection();
/* Get some records from your database */
Statement stmt = con.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY);
ResultSet rs = stmt.executeQuery(populateRangeQuery(from, until, set, 0, maxListSize));
// rs.last();
// int numRows = rs.getRow();
// rs.beforeFirst();
int count;
/* load the headers and identifiers ArrayLists. */
for (count=0; count < maxListSize && rs.next(); ++count) {
HashMap nativeItem = new HashMap();
nativeItem.put("coreResult", getColumnValues(rs));
/* Use the RecordFactory to extract header/identifier pairs for each item */
// RecordFactory rf = getRecordFactory();
Iterator setSpecs = getSetSpecs(nativeItem);
String[] header = getRecordFactory().createHeader(nativeItem, setSpecs);
headers.add(header[0]);
identifiers.add(header[1]);
}
if (count == 0) {
endConnection(con);
throw new NoItemsMatchException();
}
/* decide if you're done */
// if (count == maxListSize) {
if (rs.next()) {
// String resumptionId = getResumptionId();
// resumptionResults.put(resumptionId, rs);
/*****************************************************************
* Construct the resumptionToken String however you see fit.
*****************************************************************/
StringBuffer resumptionTokenSb = new StringBuffer();
// resumptionTokenSb.append(resumptionId);
// resumptionTokenSb.append("!");
resumptionTokenSb.append(from);
resumptionTokenSb.append("!");
resumptionTokenSb.append(until);
resumptionTokenSb.append("!");
if (set == null)
resumptionTokenSb.append(".");
else
resumptionTokenSb.append(URLEncoder.encode(set, "UTF-8"));
resumptionTokenSb.append("!");
resumptionTokenSb.append(Integer.toString(count));
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(),
-1,
0));
// listIdentifiersMap.put("resumptionMap",
// getResumptionMap(resumptionTokenSb.toString()));
endConnection(con);
}
} catch (SQLException e) {
if (con != null)
endConnection(con);
e.printStackTrace();
throw new OAIInternalServerError(e.getMessage());
} catch (UnsupportedEncodingException e) {
if (con != null)
endConnection(con);
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
****************************/
StringTokenizer tokenizer = new StringTokenizer(resumptionToken, "!");
// String resumptionId;
String from;
String until;
String set;
int oldCount;
String metadataPrefix;
// int numRows;
try {
// resumptionId = tokenizer.nextToken();
from = tokenizer.nextToken();
until = tokenizer.nextToken();
set = tokenizer.nextToken();
if (set.equals("."))
set = null;
oldCount = Integer.parseInt(tokenizer.nextToken());
// numRows = Integer.parseInt(tokenizer.nextToken());
metadataPrefix = tokenizer.nextToken();
if (debug) {
System.out.println("ExtendedJDBCOAICatalog.listIdentifiers: set=>"
+ set + "<");
}
} catch (NoSuchElementException e) {
throw new BadResumptionTokenException();
}
Connection con = null;
try {
con = startConnection();
/* Get some more records from your database */
Statement stmt = con.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY);
ResultSet rs = stmt.executeQuery(populateRangeQuery(from, until, set,
oldCount, maxListSize));
int count;
/* load the headers and identifiers ArrayLists. */
for (count = 0; count < maxListSize && rs.next(); ++count) {
HashMap nativeItem = new HashMap();
nativeItem.put("coreResult", getColumnValues(rs));
/* 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 (count == maxListSize) {
if (rs.next()) {
/*****************************************************************
* Construct the resumptionToken String however you see fit.
*****************************************************************/
StringBuffer resumptionTokenSb = new StringBuffer();
// resumptionTokenSb.append(resumptionId);
// resumptionTokenSb.append("!");
resumptionTokenSb.append(from);
resumptionTokenSb.append("!");
resumptionTokenSb.append(until);
resumptionTokenSb.append("!");
if (set == null)
resumptionTokenSb.append(".");
else
resumptionTokenSb.append(URLEncoder.encode(set, "UTF-8"));
resumptionTokenSb.append("!");
resumptionTokenSb.append(Integer.toString(oldCount + count));
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(),
-1,
oldCount));
// listIdentifiersMap.put("resumptionMap",
// getResumptionMap(resumptionTokenSb.toString()));
endConnection(con);
}
} catch (UnsupportedEncodingException e) {
if (con != null)
endConnection(con);
e.printStackTrace();
throw new OAIInternalServerError(e.getMessage());
} catch (SQLException e) {
if (con != null)
endConnection(con);
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 {
Connection con = null;
try {
con = startConnection();
Statement stmt = con.createStatement();
ResultSet rs =
stmt.executeQuery(populateIdentifierQuery(oaiIdentifier));
if (!rs.next()) {
endConnection(con);
throw new IdDoesNotExistException(oaiIdentifier);
}
HashMap nativeItem = new HashMap();
nativeItem.put("coreResult", getColumnValues(rs));
extendItem(con, nativeItem);
endConnection(con);
return constructRecord(nativeItem, metadataPrefix);
} catch (SQLException e) {
if (con != null)
endConnection(con);
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();
Connection con = null;
try {
con = startConnection();
/* Get some records from your database */
Statement stmt = con.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE,
ResultSet.CONCUR_READ_ONLY);
ResultSet rs = stmt.executeQuery(populateRangeQuery(from, until, set, 0, maxListSize));
// rs.last();
// int numRows = rs.getRow();
// rs.beforeFirst();
int count;
// if (debug) System.out.println("ExtendedJDBCOAICatalog.listRecords: numRows=" + numRows);
/* load the records ArrayList */
for (count=0; count < maxListSize && rs.next(); ++count) {
HashMap nativeItem = new HashMap();
nativeItem.put("coreResult", getColumnValues(rs));
extendItem(con, nativeItem);
String record = constructRecord(nativeItem, metadataPrefix);
records.add(record);
}
if (count == 0) {
endConnection(con);
throw new NoItemsMatchException();
}
/* decide if you're done */
// if (count == maxListSize) {
if (rs.next()) {
// String resumptionId = getResumptionId();
// resumptionResults.put(resumptionId, rs);
/*****************************************************************
* Construct the resumptionToken String however you see fit.
*****************************************************************/
StringBuffer resumptionTokenSb = new StringBuffer();
// resumptionTokenSb.append(resumptionId);
// resumptionTokenSb.append("!");
resumptionTokenSb.append(from);
resumptionTokenSb.append("!");
resumptionTokenSb.append(until);
resumptionTokenSb.append("!");
if (set == null)
resumptionTokenSb.append(".");
else
resumptionTokenSb.append(URLEncoder.encode(set, "UTF-8"));
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(),
-1,
0));
// listRecordsMap.put("resumptionMap",
// getResumptionMap(resumptionTokenSbSb.toString()));
endConnection(con);
}
} catch (UnsupportedEncodingException e) {
if (con != null)
endConnection(con);
e.printStackTrace();
throw new OAIInternalServerError(e.getMessage());
} catch (SQLException e) {
if (con != null)
endConnection(con);
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
****************************/
StringTokenizer tokenizer = new StringTokenizer(resumptionToken, "!");
// String resumptionId;
String from;
String until;
String set;
int oldCount;
// int numRows;
String metadataPrefix;
try {
// resumptionId = tokenizer.nextToken();
from = tokenizer.nextToken();
until = tokenizer.nextToken();
set = tokenizer.nextToken();
if (set.equals("."))
set = null;
oldCount = Integer.parseInt(tokenizer.nextToken());
// numRows = Integer.parseInt(tokenizer.nextToken());
metadataPrefix = tokenizer.nextToken();
} catch (NoSuchElementException e) {
throw new BadResumptionTokenException();
}
Connection con = null;
try {
con = startConnection();
/* Get some more records from your database */
Statement stmt = con.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY);
ResultSet rs = stmt.executeQuery(populateRangeQuery(from, until, set,
oldCount, maxListSize));
int count;
/* load the headers and identifiers ArrayLists. */
for (count = 0; count < maxListSize && rs.next(); ++count) {
try {
HashMap nativeItem = new HashMap();
nativeItem.put("coreResult", getColumnValues(rs));
extendItem(con, nativeItem);
String record = constructRecord(nativeItem, metadataPrefix);
records.add(record);
} catch (CannotDisseminateFormatException e) {
/* the client hacked the resumptionToken beyond repair */
endConnection(con);
throw new BadResumptionTokenException();
}
}
/* decide if you're done */
// if (count == maxListSize) {
if (rs.next()) {
/*****************************************************************
* Construct the resumptionToken String however you see fit.
*****************************************************************/
StringBuffer resumptionTokenSb = new StringBuffer();
// resumptionTokenSb.append(resumptionId);
// resumptionTokenSb.append("!");
resumptionTokenSb.append(from);
resumptionTokenSb.append("!");
resumptionTokenSb.append(until);
resumptionTokenSb.append("!");
if (set == null)
resumptionTokenSb.append(".");
else
resumptionTokenSb.append(URLEncoder.encode(set, "UTF-8"));
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(),
-1,
oldCount));
// listRecordsMap.put("resumptionMap",
// getResumptionMap(resumptionTokenSb.toString()));
endConnection(con);
}
} catch (UnsupportedEncodingException e) {
if (con != null)
endConnection(con);
e.printStackTrace();
throw new OAIInternalServerError(e.getMessage());
} catch (SQLException e) {
if (con != null)
endConnection(con);
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 {
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();
Connection con = null;
try {
if (debug) System.out.println(setQuery);
con = startConnection();
/* Get some records from your database */
Statement stmt = con.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY);
ResultSet rs = stmt.executeQuery(setQuery);
rs.last();
int numRows = rs.getRow();
rs.beforeFirst();
int count;
// if (debug) System.out.println("ExtendedJDBCOAICatalog.listSets: numRows=" + numRows);
/* load the sets ArrayLists. */
for (count=0; count < maxListSize && rs.next(); ++count) {
/* Use the RecordFactory to extract header/set pairs for each item */
HashMap nativeItem = getColumnValues(rs);
sets.add(getSetXML(nativeItem));
if (debug) System.out.println("ExtendedJDBCOAICatalog.listSets: adding an entry");
}
/* decide if you're done */
if (count < numRows) {
String resumptionId = getResumptionId();
resumptionResults.put(resumptionId, rs);
/*****************************************************************
* 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));
// listSetsMap.put("resumptionMap",
// getResumptionMap(resumptionTokenSb.toString()));
endConnection(con);
}
} catch (SQLException e) {
if (con != null)
endConnection(con);
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 {
if (setQuery == null) {
throw new BadResumptionTokenException();
} else {
purge(); // clean out old resumptionTokens
Map listSetsMap = new HashMap();
ArrayList sets = new ArrayList();
/****************************
* parse your resumptionToken
****************************/
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 */
ResultSet rs = (ResultSet)resumptionResults.get(resumptionId);
if (rs == null) {
throw new BadResumptionTokenException();
}
if (rs.getRow() != oldCount) {
// System.out.println("ExtendedJDBCOAICatalog.listIdentifiers: reuse of old resumptionToken?");
rs.absolute(oldCount);
}
int count;
/* load the sets ArrayLists. */
for (count = 0; count < maxListSize && rs.next(); ++count) {
HashMap nativeItem = getColumnValues(rs);
/* 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));
// listSetsMap.put("resumptionMap",
// getResumptionMap(resumptionTokenSb.toString()));
}
} catch (SQLException e) {
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 {
Connection con = null;
try {
ArrayList setSpecs = new ArrayList();
if (setSpecQuery != null) {
con = startConnection();
RecordFactory rf = getRecordFactory();
String oaiIdentifier = rf.getOAIIdentifier(nativeItem);
Statement stmt = con.createStatement();
ResultSet rs = stmt.executeQuery(populateSetSpecQuery(oaiIdentifier));
while (rs.next()) {
HashMap setMap = getColumnValues(rs);
setSpecs.add(setMap.get(setSpecItemLabel).toString());
}
endConnection(con);
}
return setSpecs.iterator();
} catch (SQLException e) {
if (con != null)
endConnection(con);
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 {
Connection con = null;
try {
ArrayList abouts = new ArrayList();
if (aboutQuery != null) {
con = startConnection();
RecordFactory rf = getRecordFactory();
String oaiIdentifier = rf.getOAIIdentifier(nativeItem);
Statement stmt = con.createStatement();
ResultSet rs = stmt.executeQuery(populateAboutQuery(oaiIdentifier));
while (rs.next()) {
HashMap aboutMap = getColumnValues(rs);
abouts.add(aboutMap.get(aboutValueLabel));
}
endConnection(con);
}
return abouts.iterator();
} catch (SQLException e) {
if (con != null)
endConnection(con);
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 {
// try {
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();
// } catch (SQLException e) {
// e.printStackTrace();
// throw new IllegalArgumentException(e.getMessage());
// }
}
/**
* 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) {
e.printStackTrace();
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);
}
/**
* close the repository
*/
public void close() {
try {
if (persistentConnection != null)
persistentConnection.close();
} catch (SQLException e) {
e.printStackTrace();
}
persistentConnection = null;
}
/**
* 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