/**
 *Copyright (c) 2000-2002 OCLC Online Computer Library Center,
 *Inc. and other contributors. All rights reserved.  The contents of this file, as updated
 *from time to time by the OCLC Office of Research, are subject to OCLC Research
 *Public License Version 2.0 (the "License"); you may not use this file except in
 *compliance with the License. You may obtain a current copy of the License at
 *http://purl.oclc.org/oclc/research/ORPL/.  Software distributed under the License is
 *distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express
 *or implied. See the License for the specific language governing rights and limitations
 *under the License.  This software consists of voluntary contributions made by many
 *individuals on behalf of OCLC Research. For more information on OCLC Research,
 *please see http://www.oclc.org/oclc/research/.
 *
 *The Original Code is SRUOAICatalog.java.
 *The Initial Developer of the Original Code is Jeff Young.
 *Portions created by ______________________ are
 *Copyright (C) _____ _______________________. All Rights Reserved.
 *Contributor(s):______________________________________.
 */

package org.oclc.oai.server.catalog;

// import gov.loc.www.zing.sru.interfaces.SRUPort;
// import gov.loc.www.zing.sru.RecordType;
// import gov.loc.www.zing.sru.RecordsType;
// import gov.loc.www.zing.sru.SearchRetrieveRequestType;
// import gov.loc.www.zing.sru.SearchRetrieveResponseType;
// import gov.loc.www.zing.sru.service.SRUSampleServiceLocator;

import java.io.*;
import java.net.URL;
import java.net.URLEncoder;
// import java.rmi.RemoteException;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Properties;
import java.util.StringTokenizer;
import java.util.TreeMap;
import java.util.Vector;

import javax.servlet.ServletException;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
// import javax.xml.rpc.ServiceException;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

// import org.apache.axis.message.MessageElement;
// import org.apache.axis.types.NonNegativeInteger;
// import org.apache.axis.types.PositiveInteger;
// import org.apache.log4j.BasicConfigurator;
// import org.apache.log4j.Logger;
import org.apache.xpath.XPathAPI;

import org.w3c.dom.Document;
import org.w3c.dom.DOMImplementation;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
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;

// import ORG.oclc.os.SRU.SRUDatabase;
// import ORG.oclc.os.SRU.SRUPearsDatabase;

public class SRUOAICatalog extends AbstractCatalog {
    private static final boolean debug = true;
//     private SRUPort port;
    private String sruURL;
    private String sortKeys = "";
    protected int maxListSize;
    private static String maxDate = ServerVerb.createResponseDate(new Date());
    private TreeMap sets = null;
//     private static SRUSampleServiceLocator service = new SRUSampleServiceLocator();
    private static TransformerFactory transformerFactory = TransformerFactory.newInstance();
    private static Transformer transformer = null;
    private static DocumentBuilder builder = null;
    private static Element xmlnsEl = null;
//     private static Logger logger = Logger.getLogger(SRUOAICatalog.class);
    private static DocumentBuilderFactory factory =
	DocumentBuilderFactory.newInstance();

    static {
        try {
// 	    BasicConfigurator.configure();
	    transformer = transformerFactory.newTransformer();
            factory.setNamespaceAware(true);
            builder = factory.newDocumentBuilder();
            DOMImplementation impl = builder.getDOMImplementation();
            Document xmlnsDoc
                = impl.createDocument("http://www.oclc.org/research/software/oai/harvester",
                                      "harvester:xmlnsDoc", null);
            xmlnsEl = xmlnsDoc.getDocumentElement();
//             xmlnsEl.setAttributeNS("http://www.w3.org/2000/xmlns/",
//                                    "xmlns:harvester",
//                                    "http://www.oclc.org/research/software/oai/harvester");
//             xmlnsEl.setAttributeNS("http://www.w3.org/2000/xmlns/",
//                                    "xmlns:xsd",
//                                    "http://www.w3.org/2001/XMLSchema");
//             xmlnsEl.setAttributeNS("http://www.w3.org/2000/xmlns/",
//                                    "xmlns:xsi",
//                                    "http://www.w3.org/2001/XMLSchema-instance");
//             xmlnsEl.setAttributeNS("http://www.w3.org/2000/xmlns/",
//                                    "xmlns:oai20",
//                                    "http://www.openarchives.org/OAI/2.0/");
//             xmlnsEl.setAttributeNS("http://www.w3.org/2000/xmlns/",
//                                    "xmlns:oai11_GetRecord",
//                                    "http://www.openarchives.org/OAI/1.1/OAI_GetRecord");
//             xmlnsEl.setAttributeNS("http://www.w3.org/2000/xmlns/",
//                                    "xmlns:oai11_Identify",
//                                    "http://www.openarchives.org/OAI/1.1/OAI_Identify");
//             xmlnsEl.setAttributeNS("http://www.w3.org/2000/xmlns/",
//                                    "xmlns:oai11_ListMetadataFormats",
//                                    "http://www.openarchives.org/OAI/1.1/OAI_ListMetadataFormats");
//             xmlnsEl.setAttributeNS("http://www.w3.org/2000/xmlns/",
//                                    "xmlns:oai_dc",
//                                    "http://www.openarchives.org/OAI/2.0/oai_dc/");
//             xmlnsEl.setAttributeNS("http://www.w3.org/2000/xmlns/",
//                                    "xmlns:dc",
//                                    "http://purl.org/dc/elements/1.1/");
//             xmlnsEl.setAttributeNS("http://www.w3.org/2000/xmlns/",
//                                    "xmlns:customERRoL",
//                                    "http://errol.oclc.org/oai:xmlregistry.oclc.org:errol/customERRoLSchema");
//             xmlnsEl.setAttributeNS("http://www.w3.org/2000/xmlns/",
//                                    "xmlns:xoaih",
//                                    "http://errol.oclc.org/oai:xmlregistry.oclc.org:xoai/xoaiharvester");
	    xmlnsEl.setAttributeNS("http://www.w3.org/2000/xmlns/",
				   "xmlns:srw",
				   "http://www.loc.gov/zing/srw/");
//             xmlnsEl.setAttributeNS("http://www.w3.org/2000/xmlns/",
//                                    "xmlns:explain",
//                                    "http://explain.z3950.org/dtd/2.0/");
        } catch (Exception e) {
//             logger.fatal("SRUOAICatalog static init", e);
	    e.printStackTrace();
        }
    }
    
    public SRUOAICatalog(Properties properties) throws IOException {
	this(properties, properties.getProperty("SRUOAICatalog.sruURL"));
    }
    
    public SRUOAICatalog(Properties properties, String sruURL) throws IOException {
	sortKeys = properties.getProperty("SRUOAICatalog.sortKeys");
// 	logger.debug("sortKeys=" + sortKeys);
	if (sortKeys == null) sortKeys = "";
	
	String maxListSize =
	    properties.getProperty("SRUOAICatalog.maxListSize");
// 	logger.debug("maxListSize=" + maxListSize);
	
	if (maxListSize == null) {
	    throw new IllegalArgumentException("SRUOAICatalog.maxListSize is missing from the properties file");
	} else {
	    this.maxListSize = Integer.parseInt(maxListSize);
	}

	this.sruURL = sruURL;
// 	try {
// 	    URL url = new URL(sruURL);
// 	    port = service.getSRU(url);
// 	} catch (Exception e) {
// 	    logger.warn("getSRU failed for " + sruURL, e);
// 	    throw new IOException(e.getMessage());
// 	}
	sets = getSets(properties);
    }

    private static TreeMap getSets(Properties properties) {
	TreeMap treeMap = new TreeMap();
	return treeMap;
    }

    private String normalizeTerm(String term) {
	return term;
// 	StringBuffer sb = new StringBuffer();
// 	StringTokenizer tokenizer = new StringTokenizer(term, "-");
// 	while (tokenizer.hasMoreTokens()) {
// 	    sb.append(tokenizer.nextToken());
// 	}
// 	return sb.toString();
    }

    public Map listSets() throws NoSetHierarchyException {
	if (sets.size() == 0)
	    throw new NoSetHierarchyException();
	Map listSetsMap = new LinkedHashMap();
	try {
	    Object[] keys = sets.keySet().toArray();
	    Object[] values = sets.values().toArray();
	    ArrayList newList = new ArrayList();
	    for (int i = 0; i < keys.length; ++i) {
		newList.add((String) values[i]);
	    }
	    listSetsMap.put("sets", newList.iterator());
	} catch (Throwable e) {
	    System.err.println("SRUOAICatalog.listSets: browse failed");
	    e.printStackTrace();
	}

	return listSetsMap;
    }

    public Map listSets(String resumptionToken)
	throws BadResumptionTokenException {
	throw new BadResumptionTokenException();
    }

    /**
     * get a map containing a key="headers" value=Iterator and
     * a key="resumptionToken" value=String. The "headers" Map
     * contains Map.Entrys where key="header" value="deleted" or null.
     */
    public Map listIdentifiers(String from,
			       String until,
			       String set,
			       String metadataPrefix)
	throws
	    BadArgumentException,
	    CannotDisseminateFormatException,
	    NoItemsMatchException,
	    NoSetHierarchyException,
	    OAIInternalServerError {
	if (set != null
	    && set.length() > 0
	    && from.equals(toFinestFrom("0000-00-00"))
	    && until.equals(toFinestUntil("9999-99-99"))) {
	    from = null;
	    until = null;
	}
	Map listIdentifiersMap = new HashMap();
	ArrayList headers = new ArrayList();
	ArrayList identifiers = new ArrayList();
	Document srResponse;
 	try {
	    srResponse =
		getSearchRetrieveResponse(sruURL,
					  from,
					  until,
					  set,
					  "http://www.openarchives.org/OAI/2.0/#header",
					  1,
					  maxListSize,
					  "xml");
	} catch (IOException e) {
	    e.printStackTrace();
	    throw new OAIInternalServerError(e.getMessage());
	} catch (SAXException e) {
	    e.printStackTrace();
	    throw new OAIInternalServerError(e.getMessage());
	} catch (ParserConfigurationException e) {
	    e.printStackTrace();
	    throw new OAIInternalServerError(e.getMessage());
	}
	try {
	    NodeList nodeList = getRecords(srResponse);
	    if (nodeList != null) {
		RecordFactory recordFactory = getRecordFactory();
		for (int i = 0; i < nodeList.getLength(); ++i) {
		    Object rec = getRecordData(nodeList.item(i));
		    HashMap hashMap = new HashMap();
		    hashMap.put("header", rec);
		    String localIdentifier = getRecordFactory().getLocalIdentifier(hashMap);
		    try {
			hashMap.put("metadata", getNativeMetadata(localIdentifier,
								  metadataPrefix));
		    } catch (IOException e) {
			e.printStackTrace();
			throw new OAIInternalServerError(e.getMessage());
		    } catch (TransformerException e) {
			e.printStackTrace();
			throw new OAIInternalServerError(e.getMessage());
		    } catch (ParserConfigurationException e) {
			e.printStackTrace();
			throw new OAIInternalServerError(e.getMessage());
		    } catch (SAXException e) {
			e.printStackTrace();
			throw new OAIInternalServerError(e.getMessage());
		    }
		    String[] header = recordFactory.createHeader(hashMap);
		    headers.add(header[0]);
		    identifiers.add(header[1]);
// 		MessageElement[] elems = rec.get_any();
// 		if (elems.length > 0) {
// 		    String[] header = recordFactory.createHeader(elems[0]);
// 		    headers.add(header[0]);
// 		    identifiers.add(header[1]);
// 		}
		}
		try {
		    if (getNumberOfRecords(srResponse) > nodeList.getLength()) {
			StringBuffer resumptionToken = new StringBuffer();
			resumptionToken.append(from);
			resumptionToken.append(":");
			resumptionToken.append(until);
			resumptionToken.append(":");
			resumptionToken.append(set);
			resumptionToken.append(":");
			resumptionToken.append(metadataPrefix);
			resumptionToken.append(":");
			resumptionToken.append(nodeList.getLength());
			listIdentifiersMap.put(
					       "resumptionMap",
					       getResumptionMap(resumptionToken.toString()));
		    }
		} catch (TransformerException e) {
// 		    logger.warn("failure", e);
		    e.printStackTrace();
		    throw new OAIInternalServerError(e.getMessage());
		}
	    } else {
		throw new NoItemsMatchException();
	    }
	    listIdentifiersMap.put("headers", headers.iterator());
	    listIdentifiersMap.put("identifiers", identifiers.iterator());
	    return listIdentifiersMap;
	} catch (TransformerException e) {
// 	    logger.warn("failure", e);
	    e.printStackTrace();
	    throw new OAIInternalServerError(e.getMessage());
	} catch (SAXException e) {
// 	    logger.warn("failure", e);
	    e.printStackTrace();
	    throw new OAIInternalServerError(e.getMessage());
	} catch (IOException e) {
// 	    logger.warn("failure", e);
	    e.printStackTrace();
	    throw new OAIInternalServerError(e.getMessage());
	}
    }

    /**
     * get a map containing a key="headers" value=Iterator and
     * a key="resumptionToken" value=String. The "headers" Iterator
     * contains Map.Entrys where key="header" value="deleted" or null.
     */
    public Map listIdentifiers(String resumptionToken)
	throws BadResumptionTokenException, OAIInternalServerError {
	StringTokenizer tokenizer = new StringTokenizer(resumptionToken, ":");
	String from;
	String until;
	String set;
	String metadataPrefix;
	int oldCount;
	try {
	    from = tokenizer.nextToken();
	    until = tokenizer.nextToken();
	    set = tokenizer.nextToken();
	    if ("null".equals(set))
		set = null;
	    metadataPrefix = tokenizer.nextToken();
	    oldCount = Integer.parseInt(tokenizer.nextToken());
	    if (metadataPrefix.equals("null"))
		metadataPrefix = null;
	} catch (NoSuchElementException e) {
	    throw new BadResumptionTokenException();
	}
	Map listIdentifiersMap = new HashMap();
	ArrayList headers = new ArrayList();
	ArrayList identifiers = new ArrayList();
	try {
	    Document srResponse;
 	    try {
		srResponse =
		    getSearchRetrieveResponse(sruURL,
					      from,
					      until,
					      set,
				      "http://www.openarchives.org/OAI/2.0/#header",
					      oldCount,
					      maxListSize,
					      "xml");
	    } catch (IOException e) {
		e.printStackTrace();
		throw new OAIInternalServerError(e.getMessage());
	    } catch (SAXException e) {
		e.printStackTrace();
		throw new OAIInternalServerError(e.getMessage());
	    } catch (ParserConfigurationException e) {
		e.printStackTrace();
		throw new OAIInternalServerError(e.getMessage());
	    }
	    NodeList nodeList = getRecords(srResponse);
	    if (nodeList != null) {
		RecordFactory recordFactory = getRecordFactory();
		for (int i = 0; i < nodeList.getLength(); ++i) {
		    
 		    Object rec = getRecordData(nodeList.item(i));
		    HashMap hashMap = new HashMap();
		    hashMap.put("header", rec);
		    String localIdentifier = getRecordFactory().getLocalIdentifier(hashMap);
		    hashMap.put("metadata", getNativeMetadata(localIdentifier,
							      metadataPrefix));
		    String[] header = recordFactory.createHeader(hashMap);
		    headers.add(header[0]);
		    identifiers.add(header[1]);
// 		    MessageElement[] elems = rec.get_any();
// 		    if (elems.length > 0) {
// 			String[] header = recordFactory.createHeader(elems[0]);
// 			headers.add(header[0]);
// 			identifiers.add(header[1]);
// 		    }
		}
		if (getNumberOfRecords(srResponse)
		    > oldCount + nodeList.getLength()) {
		    StringBuffer newResumptionToken = new StringBuffer();
		    newResumptionToken.append(from);
		    newResumptionToken.append(":");
		    newResumptionToken.append(until);
		    newResumptionToken.append(":");
		    newResumptionToken.append(set);
		    newResumptionToken.append(":");
		    newResumptionToken.append(metadataPrefix);
		    newResumptionToken.append(":");
		    newResumptionToken.append(oldCount + nodeList.getLength());
		    listIdentifiersMap.put(
					   "resumptionMap",
					   getResumptionMap(newResumptionToken.toString()));
		}
	    }
	    listIdentifiersMap.put("headers", headers.iterator());
	    listIdentifiersMap.put("identifiers", identifiers.iterator());
	} catch (Throwable e) {
	    e.printStackTrace();
	    throw new OAIInternalServerError("Database Failure");
	}
	return listIdentifiersMap;
    }

    /**
     * get an Iterator containing Map.Entrys where key=metadataPrefix
     * and value=schema.
     */
    public Vector getSchemaLocations(String oaiIdentifier)
	throws
	    IdDoesNotExistException,
	    NoMetadataFormatsException,
	    OAIInternalServerError {
	Vector result = new Vector();
	NodeList nodeList = null;
	try {
	    nodeList = getIdentifierRecords(sruURL, oaiIdentifier, (String) null);
	} catch (Throwable e) {
	    throw new OAIInternalServerError("Database failure");
	}
	if (nodeList != null) {
	    for (int i = 0; i < nodeList.getLength(); ++i) {
		try {
		    Object rec = getRecordData(nodeList.item(i));
		    if (rec != null) {
			Vector schemaLocations =
			    getRecordFactory().getSchemaLocations(rec);
			for (int j = 0; j < schemaLocations.size(); ++j) {
			    result.add(schemaLocations.get(j));
			}
		    } else {
			throw new OAIInternalServerError("Null Record");
		    }
		} catch (TransformerException e) {
// 		    logger.warn("failure", e);
		    e.printStackTrace();
		    throw new OAIInternalServerError(e.getMessage());
		} catch (SAXException e) {
// 		    logger.warn("failure", e);
		    e.printStackTrace();
		    throw new OAIInternalServerError(e.getMessage());
		} catch (IOException e) {
// 		    logger.warn("failure", e);
		    e.printStackTrace();
		    throw new OAIInternalServerError(e.getMessage());
		}
	    }
	} else {
	    throw new IdDoesNotExistException(oaiIdentifier);
	}
	return result;
    }

    /**
     * get a DocumentFragment containing the specified record
     */
    public String getRecord(String oaiIdentifier, String metadataPrefix)
	throws
	    IdDoesNotExistException,
	    IdDoesNotExistException,
	    CannotDisseminateFormatException,
	    OAIInternalServerError {
	Object nativeObject;
// 	logger.debug("getRecord: oaiIdentifier=" + oaiIdentifier);
 	try {
	    nativeObject = getFullRecord(sruURL, oaiIdentifier, metadataPrefix);
	} catch (IOException e) {
 	    e.printStackTrace();
 	    throw new OAIInternalServerError(e.getMessage());
	} catch (ParserConfigurationException e) {
 	    e.printStackTrace();
 	    throw new OAIInternalServerError(e.getMessage());
	} catch (TransformerException e) {
 	    e.printStackTrace();
 	    throw new OAIInternalServerError(e.getMessage());
 	} catch (SAXException e) {
 	    e.printStackTrace();
 	    throw new OAIInternalServerError(e.getMessage());
 	}

// 	logger.debug("nativeObject=" + nativeObject);

	if (nativeObject != null) {
	    String schemaURL = null;

	    if (metadataPrefix != null) {
		if ((schemaURL = getCrosswalks().getSchemaURL(metadataPrefix))
		    == null) {
// 		    logger.info("metadataPrefix not found:" + metadataPrefix);
		    throw new CannotDisseminateFormatException(metadataPrefix);
		}
	    }
	    try {
		String s =
		    getRecordFactory().create(
					      nativeObject,
					      schemaURL,
					      metadataPrefix);
// 		logger.debug("s=" + s);
		return s;
	    } catch (CannotDisseminateFormatException e) {
		e.printStackTrace();
		throw e;
	    }
	} else {
	    throw new IdDoesNotExistException(oaiIdentifier);
	}
    }

    /**
     * get a DocumentFragment containing the specified record
     */
    public String getMetadata(String oaiIdentifier, String metadataPrefix)
	throws
	    IdDoesNotExistException,
	    IdDoesNotExistException,
	    CannotDisseminateFormatException,
	    OAIInternalServerError {
	Object nativeObject;
 	try {
	    nativeObject = getFullRecord(sruURL, oaiIdentifier, metadataPrefix);
 	} catch (IOException e) {
 	    e.printStackTrace();
 	    throw new OAIInternalServerError(e.getMessage());
 	} catch (ParserConfigurationException e) {
 	    e.printStackTrace();
 	    throw new OAIInternalServerError(e.getMessage());
 	} catch (TransformerException e) {
 	    e.printStackTrace();
 	    throw new OAIInternalServerError(e.getMessage());
 	} catch (SAXException e) {
 	    e.printStackTrace();
 	    throw new OAIInternalServerError(e.getMessage());
 	}

	if (debug) {
	    System.out.println(nativeObject);
	}
	if (nativeObject != null) {
	    String schemaURL = null;

	    if (metadataPrefix != null) {
		if ((schemaURL = getCrosswalks().getSchemaURL(metadataPrefix))
		    == null) {
		    if (debug) {
			System.out.println(
					   "SRUOAICatalog.getRecord: metadataPrefix not found");
		    }
		    throw new CannotDisseminateFormatException(metadataPrefix);
		}
	    }
	    try {
		return getRecordFactory().createMetadata(
							 nativeObject,
							 schemaURL,
							 metadataPrefix);
	    } catch (CannotDisseminateFormatException e) {
		e.printStackTrace();
		throw e;
	    }
	} else {
	    throw new IdDoesNotExistException(oaiIdentifier);
	}
    }

    private NodeList getIdentifierRecords(String sruURL,
					      String oaiIdentifier,
					  String metadataPrefix)
	throws TransformerException, SAXException, IOException,
	       ParserConfigurationException {
// 	logger.debug("getIdentifierRecords: oaiIdentifier=" + oaiIdentifier);
	Document srResponse;
	String localIdentifier = getRecordFactory().fromOAIIdentifier(oaiIdentifier);
	srResponse =
	    getSearchRetrieveResponse(sruURL,
				      localIdentifier,
				      "http://www.openarchives.org/OAI/2.0/#header",
				      "xml");
	return getRecords(srResponse);
    }

    private Object getFullRecord(String sruURL,
				 String oaiIdentifier,
				  String metadataPrefix)
	throws SAXException, TransformerException, IOException,
	       ParserConfigurationException {
// 	logger.debug("getFullRecord: oaiIdentifier=" + oaiIdentifier + " metadataPrefix=" + metadataPrefix);
        NodeList nodeList = getIdentifierRecords(sruURL, oaiIdentifier, metadataPrefix);
	if (nodeList != null) {
// 	    logger.debug("getFullRecord: nodeList.getLength()=" + nodeList.getLength());
	    HashMap hashMap = new HashMap();
// 	    logger.debug("store header");
	    hashMap.put("header", getRecordData(nodeList.item(0)));
	    String nativeRecordSchema = getRecordFactory().getCrosswalks().getNativeRecordSchema(metadataPrefix);
	    String localIdentifier = getRecordFactory().fromOAIIdentifier(oaiIdentifier);
// 	    logger.debug("getFullRecord: nativeRecordSchema=" + nativeRecordSchema);
// 	    logger.debug("localIdentifier=" + localIdentifier);
	    Document srResponse =
		getSearchRetrieveResponse(sruURL,
					  localIdentifier,
					  nativeRecordSchema,
					  "xml");
	    nodeList = getRecords(srResponse);
// 	    logger.debug("store metadata");
// 	    logger.debug("nodeList.length=" + nodeList.getLength());
	    hashMap.put("metadata", getRecordData(nodeList.item(0)));
	    return hashMap;
// 	} else {
// 	    logger.debug("getFullRecord: nodeList=null");
	}
	return null;
    }

    private Object getNativeMetadata(String localIdentifier, String metadataPrefix)
	throws TransformerException, SAXException, IOException,
	       ParserConfigurationException {
// 	logger.debug("getNativeMetadata: metadataPrefix=" + metadataPrefix);
	String nativeRecordSchema = getRecordFactory().getCrosswalks().getNativeRecordSchema(metadataPrefix);
// 	logger.debug("getNativeMetadata: nativeRecordSchema=" + nativeRecordSchema);
	Document srResponse;
	srResponse =
	    getSearchRetrieveResponse(sruURL,
				      localIdentifier,
				      nativeRecordSchema,
				      "xml");
	NodeList nodeList = getRecords(srResponse);
	return getRecordData(nodeList.item(0));
    }

    public Map listRecords(String from,
			   String until,
			   String set,
			   String metadataPrefix)
	throws
	    BadArgumentException,
	    CannotDisseminateFormatException,
	    NoItemsMatchException,
	    NoSetHierarchyException,
	    OAIInternalServerError {
// 	logger.debug("SRUOAICatalog.listRecords");
	if (set != null
	    && set.length() > 0
	    && from.equals(toFinestFrom("0000-00-00"))
	    && until.equals(toFinestUntil("9999-99-99"))) {
	    from = null;
	    until = null;
	}
	Map listRecordsMap = new HashMap();
	ArrayList recordsList = new ArrayList();
// 	logger.debug("invoking searchRetrieve for #header");
	Document srResponse;
	try {
	    srResponse =
		getSearchRetrieveResponse(sruURL,
					  from,
					  until,
					  set,
				      "http://www.openarchives.org/OAI/2.0/#header",
					  1,
					  maxListSize,
					  "xml");
	} catch (IOException e) {
	    e.printStackTrace();
	    throw new OAIInternalServerError(e.getMessage());
	} catch (SAXException e) {
	    e.printStackTrace();
	    throw new OAIInternalServerError(e.getMessage());
	} catch (ParserConfigurationException e) {
	    e.printStackTrace();
	    throw new OAIInternalServerError(e.getMessage());
	}
	try {
	    NodeList nodeList = getRecords(srResponse);
// 	    logger.debug("SRUOAICatalog.listRecords: nodeList.size=" + nodeList.getLength());
	    if (nodeList != null) {
		RecordFactory recordFactory = getRecordFactory();
		String schemaURL = null;
		if (metadataPrefix != null) {
		    if ((schemaURL = getCrosswalks().getSchemaURL(metadataPrefix))
			== null)
			throw new CannotDisseminateFormatException(metadataPrefix);
		}
// 		logger.debug("schemaURL=" + schemaURL);
		for (int i = 0; i < nodeList.getLength(); ++i) {
		    Object rec = getRecordData(nodeList.item(i));
		    HashMap hashMap = new HashMap();
		    hashMap.put("header", rec);
		    String localIdentifier = getRecordFactory().getLocalIdentifier(hashMap);
// 		    logger.debug("SRUOAICatalog.listRecords: localIdentifier=" + localIdentifier);
 		    try {
			hashMap.put("metadata", getNativeMetadata(localIdentifier,
								  metadataPrefix));
 		    } catch (IOException e) {
 			e.printStackTrace();
 			throw new OAIInternalServerError(e.getMessage());
 		    } catch (ParserConfigurationException e) {
 			e.printStackTrace();
 			throw new OAIInternalServerError(e.getMessage());
 		    } catch (SAXException e) {
 			e.printStackTrace();
 			throw new OAIInternalServerError(e.getMessage());
 		    }
		    recordsList.add(
				    recordFactory.create(hashMap,
							 schemaURL,
							 metadataPrefix));
		}
		if (getNumberOfRecords(srResponse) > nodeList.getLength()) {
		    StringBuffer resumptionToken = new StringBuffer();
		    resumptionToken.append(from);
		    resumptionToken.append(":");
		    resumptionToken.append(until);
		    resumptionToken.append(":");
		    resumptionToken.append(set);
		    resumptionToken.append(":");
		    resumptionToken.append(metadataPrefix);
		    resumptionToken.append(":");
		    resumptionToken.append(nodeList.getLength());
		    listRecordsMap.put(
				       "resumptionMap",
				       getResumptionMap(resumptionToken.toString()));
		}
	    } else {
		throw new NoItemsMatchException();
	    }
	    listRecordsMap.put("records", recordsList.iterator());
	    return listRecordsMap;
	} catch (TransformerException e) {
// 	    logger.warn("failure", e);
	    e.printStackTrace();
	    throw new OAIInternalServerError(e.getMessage());
	} catch (SAXException e) {
// 	    logger.warn("failure", e);
	    e.printStackTrace();
	    throw new OAIInternalServerError(e.getMessage());
	} catch (IOException e) {
// 	    logger.warn("failure", e);
	    e.printStackTrace();
	    throw new OAIInternalServerError(e.getMessage());
	}
    }

    public Map listRecords(String resumptionToken)
	throws BadResumptionTokenException, OAIInternalServerError {
	StringTokenizer tokenizer = new StringTokenizer(resumptionToken, ":");
	String from;
	String until;
	String set;
	String metadataPrefix;
	int oldCount;
	
	try {
	    from = tokenizer.nextToken();
	    until = tokenizer.nextToken();
	    set = tokenizer.nextToken();
	    if ("null".equals(set))
		set = null;
	    metadataPrefix = tokenizer.nextToken();
	    oldCount = Integer.parseInt(tokenizer.nextToken());
	} catch (NoSuchElementException e) {
	    e.printStackTrace();
	    throw new BadResumptionTokenException();
	}
	Map listRecordsMap = new HashMap();
	ArrayList recordsList = new ArrayList();
	try {
	    Document srResponse =
		getSearchRetrieveResponse(sruURL,
					  from,
					  until,
					  set,
				      "http://www.openarchives.org/OAI/2.0/#header",
					  oldCount,
					  maxListSize,
					  "xml");
	    NodeList nodeList = getRecords(srResponse);
	    RecordFactory recordFactory = getRecordFactory();
	    String schemaURL = null;
	    if (metadataPrefix != null) {
		if ((schemaURL = getCrosswalks().getSchemaURL(metadataPrefix))
		    == null)
		    throw new CannotDisseminateFormatException(metadataPrefix);
	    }
	    for (int i = 0; i < nodeList.getLength(); ++i) {
		Object rec = getRecordData(nodeList.item(i));
		HashMap hashMap = new HashMap();
		hashMap.put("header", rec);
		String localIdentifier = getRecordFactory().getLocalIdentifier(hashMap);
		hashMap.put("metadata", getNativeMetadata(localIdentifier,
							  metadataPrefix));
		recordsList.add(
				recordFactory.create(hashMap,
						     schemaURL,
						     metadataPrefix));
	    }
	    if (getNumberOfRecords(srResponse) > oldCount + nodeList.getLength()) {
		StringBuffer newResumptionToken = new StringBuffer();
		newResumptionToken.append(from);
		newResumptionToken.append(":");
		newResumptionToken.append(until);
		newResumptionToken.append(":");
		newResumptionToken.append(set);
		newResumptionToken.append(":");
		newResumptionToken.append(metadataPrefix);
		newResumptionToken.append(":");
		newResumptionToken.append(oldCount + nodeList.getLength());
		listRecordsMap.put(
				   "resumptionMap",
				   getResumptionMap(newResumptionToken.toString()));
	    }
	    listRecordsMap.put("records", recordsList.iterator());
	} catch (Throwable e) {
	    e.printStackTrace();
	    throw new OAIInternalServerError("Database Failure");
	}
	return listRecordsMap;
    }

    private int getNumberOfRecords(Document srResponse)
    throws TransformerException {
	Node numberOfRecordsNode = XPathAPI.selectSingleNode(srResponse, "/srw:searchRetrieveResponse/srw:numberOfRecords", xmlnsEl);
	return Integer.parseInt(XPathAPI.eval(numberOfRecordsNode, "string()", xmlnsEl).str());
    }

    public void close() {
    }

    private Document
	getSearchRetrieveResponse(String sruURL,
				  String from,
				  String until,
				  String set,
				  String recordSchema,
				  int startRecord,
				  int maximumRecords,
				  String recordPacking)
	throws SAXException, ParserConfigurationException,
	       UnsupportedEncodingException, IOException {
	StringBuffer query = new StringBuffer();
	if ((from != null && from.length() > 0)
	    || (until != null && until.length() > 0)) {
	    query.append("(");
	    if (from != null && from.length() > 0) {
		query.append("oai.datestamp>=\"");
		query.append(normalizeTerm(from));
		query.append("\"");
	    }
	    if (until != null && from.length() > 0) {
		if (query.length() > 0)
		    query.append(" and ");
		query.append("oai.datestamp<=\"");
		query.append(normalizeTerm(until));
		query.append("\"");
	    }
	    query.append(")");
	}
	if (set != null && set.length() > 0) {
	    if (query.length() > 0)
		query.append(" and ");
	    query.append("oai.set=\"");
	    query.append(normalizeTerm(set));
	    query.append("\"");
	}
	StringBuffer request = new StringBuffer(sruURL);
	request.append("?operation=searchRetrieve&version=1.1&query=");
	request.append(URLEncoder.encode(query.toString(), "UTF-8"));
	request.append("&recordSchema=").append(URLEncoder.encode(recordSchema, "UTF-8"));
	request.append("&startRecord=").append(Integer.toString(startRecord));
	request.append("&maximumRecords=").append(Integer.toString(maximumRecords));
	request.append("&recordPacking=").append(recordPacking);
	request.append("&sortKeys=").append(URLEncoder.encode(sortKeys, "UTF-8"));
	DocumentBuilder builder = factory.newDocumentBuilder();
// 	logger.debug("request=" + request.toString());
	return builder.parse(request.toString());
    }

    private Document
	getSearchRetrieveResponse(String sruURL,
				  String localIdentifier,
				  String recordSchema,
				  String recordPacking)
	throws SAXException, IOException, ParserConfigurationException,
	       UnsupportedEncodingException {
// 	logger.debug("getSearchRetrieveResponse: localIdentifier=" + localIdentifier);
	StringBuffer query = new StringBuffer();
	query.append("oai.identifier exact \"");
	query.append(localIdentifier);
// 	query.append(normalizeTerm(localIdentifier));
	query.append("\"");

// 	SearchRetrieveRequestType request = new SearchRetrieveRequestType();
	StringBuffer request = new StringBuffer(sruURL);
	request.append("?operation=searchRetrieve&version=1.1&query=");
	request.append(URLEncoder.encode(query.toString(), "UTF-8"));
	request.append("&recordSchema=").append(URLEncoder.encode(recordSchema, "UTF-8"));
	request.append("&startRecord=1&maximumRecords=1");
	request.append("&recordPacking=").append(recordPacking);
	DocumentBuilder builder = factory.newDocumentBuilder();
// 	logger.debug("request=" + request.toString());
	return builder.parse(request.toString());
    }

    private Element getRecordData(Node record)
	throws TransformerException, SAXException, IOException {
	Node result = XPathAPI.selectSingleNode(record,
						"srw:recordData/*[1]",
						xmlnsEl);
// 	logger.debug("getRecordData: result=" + result);
	return toDocument((Element)result).getDocumentElement();
    }

    private Document toDocument(Element el)
	throws TransformerException, SAXException, IOException {
	DOMSource source = new DOMSource(el);
	StringWriter sw = new StringWriter();
	StreamResult result = new StreamResult(sw);
	synchronized (transformer) {
	    transformer.transform(source, result);
	}
	Document doc = builder.parse(new InputSource(new StringReader(sw.toString())));
	Element docEl = doc.getDocumentElement();
	if (docEl.getNamespaceURI() == null) {
	    docEl.setAttributeNS("http://www.w3.org/2000/xmlns/", "xmlns", "");
	}
	return doc;
    }

    private NodeList getRecords(Document srResponse)
	throws TransformerException {
	return XPathAPI.selectNodeList(srResponse, "/srw:searchRetrieveResponse/srw:records/srw:record", xmlnsEl);
// 	NodeList nodeList = null;
// 	logger.debug("#records=" + srResponse.getNumberOfRecords().intValue());
// 	if (srResponse.getNumberOfRecords().intValue() != 0) {
// 	    RecordsType records = srResponse.getRecords();
// 	    nodeList = records.getRecord();
// 	}
// 	return nodeList;
    }
}

