Index: gs3-extensions/iiif-servlet/trunk/src/PREPARE.sh
===================================================================
--- gs3-extensions/iiif-servlet/trunk/src/PREPARE.sh	(revision 32776)
+++ gs3-extensions/iiif-servlet/trunk/src/PREPARE.sh	(revision 32842)
@@ -26,4 +26,7 @@
 /bin/cp ../../web/WEB-INF/classes/OAIConfig.xml packages/cantaloupe-4.0.2/src/main/resources/.
 
+echo "Copying Greenstone's IIIFConfig.xml into Cantaloupe's resources folder"
+/bin/cp ../../web/WEB-INF/classes/IIIFConfig.xml packages/cantaloupe-4.0.2/src/main/resources/.
+
 
 echo
Index: gs3-extensions/iiif-servlet/trunk/src/gsdl-src/java/org/greenstone/gsdl3/IIIFServerBridge.java
===================================================================
--- gs3-extensions/iiif-servlet/trunk/src/gsdl-src/java/org/greenstone/gsdl3/IIIFServerBridge.java	(revision 32776)
+++ gs3-extensions/iiif-servlet/trunk/src/gsdl-src/java/org/greenstone/gsdl3/IIIFServerBridge.java	(revision 32842)
@@ -39,5 +39,5 @@
 import org.greenstone.gsdl3.util.GSParams;
 import org.greenstone.gsdl3.util.GSXML;
-import org.greenstone.gsdl3.util.OAIXML;
+import org.greenstone.gsdl3.util.IIIFXML;
 import org.greenstone.gsdl3.util.XMLConverter;
 import org.w3c.dom.Document;
@@ -53,5 +53,5 @@
  */
 /**
- * OAI server configuration instructions *
+ * IIIF server configuration instructions *
  * 
  */
@@ -71,15 +71,15 @@
         private void configure() throws UnavailableException
         {	    
-	    // Read in OAIConfig.xml (residing web/WEB-INF/classes/) and 
+	    // Read in IIIFConfig.xml (residing web/WEB-INF/classes/) and 
 	    //use it to configure the receptionist. 
-	    Element oai_config = OAIXML.getOAIConfigXML();
-	    if (oai_config == null)
-		{
-		    logger.error("Fail to parse oai config file OAIConfig.xml.");
-		    throw new UnavailableException("IIIFServerBridge: Couldn't parse OAIConfig.xml");
+	    Element iiif_config = IIIFXML.getIIIFConfigXML();
+	    if (iiif_config == null)
+		{
+		    logger.error("Fail to parse IIIF config file IIIFConfig.xml.");
+		    throw new UnavailableException("IIIFServerBridge: Couldn't parse IIIFConfig.xml");
 		}
 	    // pass it to the receptionist
-	    if (!this.recept.configure(oai_config)) {
-		logger.error("Couldn't configure receptionist");
+	    if (!this.recept.configure(iiif_config)) {
+		logger.error("Couldn't configure IIIF receptionist");
 		throw new UnavailableException("IIIFServerBridge: Couldn't configure receptionist"); 
 	    }	    
@@ -147,5 +147,5 @@
 		{
 		    logger.error("IIIFServerBridge.init Error: invalid Communicator type: " + remote_site_type);
-		    throw new UnavailableException("OAIServer: invalid communicator type");
+		    throw new UnavailableException("IIIFServerBridge: invalid communicator type");
 		}
 	    
@@ -153,5 +153,5 @@
 		{
 		    logger.error("IIIFServerBridge.init Error: Couldn't configure communicator");
-		    throw new UnavailableException("OAIServer: Couldn't configure communicator");
+		    throw new UnavailableException("IIIFServerBridge: Couldn't configure communicator");
 		}
 	    this.recept.setSiteName(remote_site_name);
@@ -175,5 +175,5 @@
 		String verb = "GetRecord";
 		Document response_doc = XMLConverter.newDOM();
-		Element xml_response = OAIXML.createBasicResponse(response_doc, verb, pairs);
+		Element xml_response = IIIFXML.createBasicResponse(response_doc, verb, pairs);
 		Element verb_elem = null;
 		
@@ -195,5 +195,5 @@
 		{
 			logger.info("xml_result is null");
-			verb_elem = OAIXML.createErrorElement(response_doc, "Internal error", "");
+			verb_elem = IIIFXML.createErrorElement(response_doc, "Internal error", "");
 			xml_response.appendChild(verb_elem);
 		}
@@ -210,5 +210,5 @@
 			{
 				logger.info("response element in xml_result is null");
-				verb_elem = OAIXML.createErrorElement(response_doc, "Internal error", "");
+				verb_elem = IIIFXML.createErrorElement(response_doc, "Internal error", "");
 			}
 			else
@@ -217,9 +217,12 @@
 			}
 
-			if ( verb_elem.getTagName().equals(OAIXML.ERROR))
+			// ******
+			xml_response.appendChild(response_doc.importNode(verb_elem, true));
+			/*
+			if ( verb_elem.getTagName().equals(IIIFXML.ERROR))
 			{
 			  xml_response.appendChild(response_doc.importNode(verb_elem, true));
 			}
-			else if (OAIXML.oai_version.equals(OAIXML.OAI_VERSION2)) {
+			else if (IIIFXML.iiif_version.equals(IIIFXML.IIIF_VERSION2)) {
 			  xml_response.appendChild(response_doc.importNode(verb_elem, true));
 			}
@@ -227,5 +230,5 @@
 			{
 				GSXML.copyAllChildren(xml_response, verb_elem);
-			}
+				}*/
 		}
 		/*
@@ -259,5 +262,5 @@
 			if (index != -1)
 			{ //just a double check
-			  Element param = GSXML.createParameter(doc, pairs[i].substring(0, index), OAIXML.oaiDecode(pairs[i].substring(index + 1)));
+			  Element param = GSXML.createParameter(doc, pairs[i].substring(0, index), IIIFXML.oaiDecode(pairs[i].substring(index + 1)));
 			  request.appendChild(param);
 			}
Index: gs3-extensions/iiif-servlet/trunk/src/gsdl-src/java/org/greenstone/gsdl3/core/IIIFMessageRouter.java
===================================================================
--- gs3-extensions/iiif-servlet/trunk/src/gsdl-src/java/org/greenstone/gsdl3/core/IIIFMessageRouter.java	(revision 32776)
+++ gs3-extensions/iiif-servlet/trunk/src/gsdl-src/java/org/greenstone/gsdl3/core/IIIFMessageRouter.java	(revision 32842)
@@ -28,5 +28,5 @@
 import org.apache.commons.lang3.StringUtils;
 import org.apache.log4j.Logger;
-import org.greenstone.gsdl3.collection.OAICollection;
+import org.greenstone.gsdl3.collection.IIIFCollection;
 import org.greenstone.gsdl3.collection.ServiceCluster;
 import org.greenstone.gsdl3.comms.Communicator;
@@ -36,5 +36,5 @@
 import org.greenstone.gsdl3.util.GSPath;
 import org.greenstone.gsdl3.util.GSXML;
-import org.greenstone.gsdl3.util.OAIXML;
+import org.greenstone.gsdl3.util.IIIFXML;
 import org.greenstone.gsdl3.util.UserContext;
 import org.greenstone.gsdl3.util.XMLConverter;
@@ -48,5 +48,5 @@
  * The hub of a Greenstone IIIF image server/bridge.
  * 
- * A simplified version of MessageRouter for OAIServer. Only loads up collections that have OAI services.
+ * A simplified version of MessageRouter for IIIFServerBridge. Only loads up collections that have IIIF services.
  */
 public class IIIFMessageRouter extends MessageRouter
@@ -55,5 +55,5 @@
   static Logger logger = Logger.getLogger(org.greenstone.gsdl3.core.IIIFMessageRouter.class.getName());
 
-  public Element oai_config = null;
+  public Element iiif_config = null;
 	//***************************************************************
 	// public methods
@@ -74,5 +74,9 @@
 		cleanUpModuleMapEntire();
 
-		// for oai, we don't do anything with the site config file. But we'll read it in and keep it in case need it later, eg for replace elements when retrieving metadata - which I don't think has been implemented
+		// ****
+		// for oai, we don't do anything with the site config
+		// file. But we'll read it in and keep it in case need
+		// it later, e.g. for replace elements when retrieving
+		// metadata - which I don't think has been implemented
 		File configFile = new File(GSFile.siteConfigFile(this.site_home));
 
@@ -92,9 +96,12 @@
 		this.config_info = config_doc.getDocumentElement();
 
-		// this is the receptionists OAIConfig.xml. Need to rethink how the MR gets this this if we ever talk to remote site, and whether it should be using it anyway
-		this.oai_config = OAIXML.getOAIConfigXML();
-		if (this.oai_config == null)
+		// this is the receptionist's IFFFConfig.xml.
+		// Need to rethink how the MR gets this this if we
+		// ever talk to remote site, and whether it should be
+		// using it anyway
+		this.iiif_config = IIIFXML.getIIIFConfigXML();
+		if (this.iiif_config == null)
 		{
-		  logger.error("Couldn't load in OAIConfig.xml");
+		  logger.error("Couldn't load in IIIFConfig.xml");
 		  return false;
 		}
@@ -122,6 +129,6 @@
 		logger.info("Activating collection: " + col_name + ".");
 		Document doc = this.collection_list.getOwnerDocument();
-		// use our special OAICollection - this will only load in oai services
-		OAICollection c = new OAICollection();
+		// use our special IIIFCollection - this will only load in IIIF services
+		IIIFCollection c = new IIIFCollection();
 
 		c.setCollectionName(col_name);
@@ -135,10 +142,10 @@
 
 		logger.info("have just configured collection " + col_name);
-		if (!c.hasOAI()) {
-		  logger.info ("collection "+col_name+" has no OAI services. Not keeping it loaded");
+		if (!c.hasIIIF()) {
+		  logger.info ("collection "+col_name+" has no IIIF services. Not keeping it loaded");
 		  return false;
 		}
-		if (!c.configureOAI(this.oai_config)) {
-		  logger.info("couldn't configure the collection : "+col_name +" with the oai config info");
+		if (!c.configureIIIF(this.iiif_config)) {
+		  logger.info("couldn't configure the collection : "+col_name +" with the iiif config info");
 		  return false;
 		}
@@ -147,7 +154,9 @@
 		Element e = doc.createElement(GSXML.COLLECTION_ELEM);
 		e.setAttribute(GSXML.NAME_ATT, col_name);
+		/*
 		e.setAttribute(OAIXML.LASTMODIFIED, "" + c.getLastmodified());
 		e.setAttribute(OAIXML.EARLIEST_DATESTAMP, "" + c.getEarliestDatestamp());
 		e.setAttribute(OAIXML.EARLIEST_OAI_DATESTAMP, "" + c.getEarliestOAIDatestamp());
+		*/
 		this.collection_list.appendChild(e);
 		return true;
@@ -168,4 +177,5 @@
 	 * @return the result Element - should be <response>
 	 */
+    /*
   protected Element processMessage(Element req) {
     Document doc = XMLConverter.newDOM();
@@ -185,4 +195,4 @@
   }
 
-
+    */
 }
Index: gs3-extensions/iiif-servlet/trunk/src/gsdl-src/java/org/greenstone/gsdl3/core/IIIFReceptionist.java
===================================================================
--- gs3-extensions/iiif-servlet/trunk/src/gsdl-src/java/org/greenstone/gsdl3/core/IIIFReceptionist.java	(revision 32776)
+++ gs3-extensions/iiif-servlet/trunk/src/gsdl-src/java/org/greenstone/gsdl3/core/IIIFReceptionist.java	(revision 32842)
@@ -46,8 +46,10 @@
   protected String site_name = null;
   /** The unique  repository identifier */
+    /*
   protected String repository_id = null;
-      
+    */
+    
   /** the configure file of this receptionist passed from the iiif restlet. */
-  protected Element oai_config = null;
+  protected Element iiif_config = null;
 
   /** the message router that the Receptionist and Actions will talk to */
@@ -96,5 +98,5 @@
   }
   /** sets the message router  - it should already be created and
-   * configured in the init() of a servlet/restlet (OAIServer, for example) before being passed to the receptionist*/
+   * configured in the init() of a servlet/restlet (IIIFServerBridge, for example) before being passed to the receptionist*/
   public void setMessageRouter(ModuleInterface mr) {
     this.mr = mr;
@@ -112,8 +114,11 @@
       return false;
     }
-    oai_config = config;
-    
+    iiif_config = config;
+
+    /*
     repository_id = getRepositoryIdentifier(); 
-
+    */
+    
+    /*
     if (!configureSetInfo()) {
       // there are no sets
@@ -121,8 +126,10 @@
       return false;
     }
-
+    */
+    
     return true;
   }
 
+    /*
   private boolean configureSetInfo() {
     this.set_set = new HashSet<String>();
@@ -235,5 +242,6 @@
     return true;
   }
-
+    */
+    
   protected void resetMessageRouter() {
     // we just need to send a configure request to MR
@@ -259,6 +267,6 @@
   }
 
-  //Compose a message/response element used to send back to the OAIServer servlet. 
-  //This method is only used within OAIReceptionist
+  //Compose a message/response element used to send back to the IIIFServerBridge servlet. 
+  //This method is only used within IIIFReceptionist
   private Element getMessage(Document doc, Element e) {
     Element msg = doc.createElement(GSXML.MESSAGE_ELEM);
@@ -281,5 +289,5 @@
     if (!message.getTagName().equals(GSXML.MESSAGE_ELEM)) {
       logger.error(" Invalid message. GSDL message should start with <"+GSXML.MESSAGE_ELEM+">, instead it starts with:"+message.getTagName()+".");
-      return OAIXML.createErrorMessage(OAIXML.BAD_ARGUMENT, "Internal messaging error");
+      return IIIFXML.createErrorMessage(OAIXML.BAD_ARGUMENT, "Internal messaging error");
     }
    
@@ -288,5 +296,5 @@
     if (request == null) {
       logger.error(" message had no request!");
-      return OAIXML.createErrorMessage(OAIXML.BAD_ARGUMENT, "Internal messaging error");
+      return IIIFXML.createErrorMessage(OAIXML.BAD_ARGUMENT, "Internal messaging error");
     }
 
@@ -295,6 +303,6 @@
     if (!reset.equals("")) {
       resetMessageRouter();
-      configureSetInfo();
-      return OAIXML.createResetResponse(true);
+      // configureSetInfo(); // ****
+      return IIIFXML.createResetResponse(true);
     }
 
@@ -303,18 +311,21 @@
     //The only thing that the oai receptionist can be sure is that these verbs are valid, nothing else.
     String verb = request.getAttribute(GSXML.TO_ATT);
+    /*
     if (verb.equals(OAIXML.IDENTIFY)) {
       return doIdentify();
-    }
+      }*/
+    
     if (verb.equals(OAIXML.GET_RECORD)) {
       return doGetRecord(request);
     }
 
-    // should never get here as verbs were checked in OAIServer
-    return OAIXML.createErrorMessage(OAIXML.BAD_VERB, "Unexpected things happened");
-    
-  }
-
+    // should never get here as verbs were checked in IIIFServerBridge
+    return IIIFXML.createErrorMessage(OAIXML.BAD_VERB, "Unexpected things happened");
+    
+  }
+
+    /*
   private String getRepositoryIdentifier() {
-    Element ri = (Element)GSXML.getChildByTagName(oai_config, OAIXML.REPOSITORY_IDENTIFIER);
+    Element ri = (Element)GSXML.getChildByTagName(iiif_config, OAIXML.REPOSITORY_IDENTIFIER);
     if (ri != null) { 
       return GSXML.getNodeText(ri);
@@ -322,8 +333,8 @@
     return "";
   }
-
+    */
 
   private void copyNamedElementfromConfig(Element to_elem, String element_name) {
-    Element original_element = (Element)GSXML.getChildByTagName(oai_config, element_name);
+    Element original_element = (Element)GSXML.getChildByTagName(iiif_config, element_name);
     if(original_element != null) {
       GSXML.copyNode(to_elem, original_element);
@@ -331,4 +342,5 @@
   }
 
+    /*
   private Element doIdentify() {
     //The validation for this verb has been done in OAIServer.validate(). So no bother here.
@@ -348,5 +360,5 @@
         
     //There can be more than one admin email according to the OAI specification
-    NodeList admin_emails = GSXML.getChildrenByTagName(oai_config, OAIXML.ADMIN_EMAIL);
+    NodeList admin_emails = GSXML.getChildrenByTagName(iiif_config, OAIXML.ADMIN_EMAIL);
     int num_admin = 0;
     Element from_admin_email = null;  
@@ -358,5 +370,5 @@
     }
 
-    /* IIIF does not have the equivalent of oai earliestDatestamp */
+    // IIIF does not have the equivalent of oai earliestDatestamp 
      
     // output the oai identifier
@@ -368,5 +380,5 @@
 
     // if there are any oaiInfo metadata, add them in too.
-    Element info = (Element)GSXML.getChildByTagName(oai_config, OAIXML.OAI_INFO);
+    Element info = (Element)GSXML.getChildByTagName(iiif_config, OAIXML.OAI_INFO);
     if (info != null) {
       NodeList meta = GSXML.getChildrenByTagName(info, OAIXML.METADATA);
@@ -383,5 +395,6 @@
     return getMessage(doc, identify);
   }
-
+    */
+    
   private Element doGetRecord(Element req){
     logger.info("");
@@ -392,5 +405,5 @@
 	*/ 
     Document doc = XMLConverter.newDOM();
-    Element get_record = doc.createElement(OAIXML.GET_RECORD);
+    Element get_record = doc.createElement(IIIFXML.GET_RECORD);
 
     HashSet<String> valid_strs = new HashSet<String>();
Index: gs3-extensions/iiif-servlet/trunk/src/gsdl-src/java/org/greenstone/gsdl3/service/IIIFPMH.java
===================================================================
--- gs3-extensions/iiif-servlet/trunk/src/gsdl-src/java/org/greenstone/gsdl3/service/IIIFPMH.java	(revision 32842)
+++ gs3-extensions/iiif-servlet/trunk/src/gsdl-src/java/org/greenstone/gsdl3/service/IIIFPMH.java	(revision 32842)
@@ -0,0 +1,977 @@
+/*
+ *    IIIFPMH.java
+ *    Copyright (C) 2019 New Zealand Digital Library, http://www.nzdl.org
+ *
+ *    This program is free software; you can redistribute it and/or modify
+ *    it under the terms of the GNU General Public License as published by
+ *    the Free Software Foundation; either version 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program is distributed in the hope that it will be useful,
+ *    but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *    GNU General Public License for more details.
+ *
+ *    You should have received a copy of the GNU General Public License
+ *    along with this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+package org.greenstone.gsdl3.service;
+
+// Greenstone classes
+import org.greenstone.gsdl3.core.GSException;
+import org.greenstone.gsdl3.util.GSXML;
+import org.greenstone.gsdl3.util.IIIFXML;
+import org.greenstone.gsdl3.util.OAIXML; // ****
+import org.greenstone.gsdl3.util.OID;
+import org.greenstone.gsdl3.util.GSFile;
+import org.greenstone.gsdl3.util.XMLConverter;
+
+import org.greenstone.gsdl3.util.SimpleCollectionDatabase;
+import org.greenstone.gsdl3.util.DBInfo;
+// XML classes
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.NodeList;
+
+// General Java classes
+import java.io.File;
+import java.util.StringTokenizer;
+import java.util.Vector;
+import java.util.Set;
+import java.util.Iterator;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map.Entry;
+
+import org.apache.log4j.Logger;
+
+/** Implements the IIIF service for GS3 collections.
+ *  Digs into each collection's database to study the metadata
+ *  and from that determine what file in a document's assocdir
+ *  can be used to provide a source image for the doc
+ *
+ */
+
+public class IIIFPMH extends ServiceRack {
+  
+  static Logger logger = Logger.getLogger(org.greenstone.gsdl3.service.IIIFPMH.class.getName());
+  
+  protected SimpleCollectionDatabase coll_db = null;
+    //protected SimpleCollectionDatabase oaiinf_db = null;
+  
+  protected String site_name = "";
+  protected String coll_name = "";
+ 
+  // set this up during configure
+  protected Element list_sets_response = null;
+  
+  protected Element meta_formats_definition = null;
+  protected HashMap<String, HashSet<String>> format_elements_map = null;
+  protected HashMap<String, Element> format_response_map = null;
+  protected HashMap<String, Element> format_meta_elem_map = null;
+  
+  protected String index_stem = "";
+  protected String infodb_type = "";
+  
+  /** constructor */
+  public IIIFPMH() {
+
+  }
+  
+  public void cleanUp() {
+    super.cleanUp();//??
+	
+    if(this.coll_db != null) {		
+	this.coll_db.closeDatabase();
+	this.coll_db = null;
+    }
+    /*
+    if (this.oaiinf_db != null){
+    	this.oaiinf_db.closeDatabase();
+	}*/
+  }
+  
+  /** configure this service 
+  info is the IIIFPMH service rack from collectionConfig.xml, and 
+  extra_info is buildConfig.xml */
+  public boolean configure(Element info, Element extra_info) {
+    if (!super.configure(info, extra_info)){
+      logger.info("Configuring IIIFPMH(extends ServiceRack) returns false.");
+      return false;
+    }
+    
+    //get the names from ServiceRack.java
+    this.site_name = this.router.getSiteName();
+    this.coll_name = this.cluster_name;
+    
+    logger.info("Configuring IIIFPMH...");
+
+    this.config_info = info;
+    
+    // the index stem is either specified in the buildConfig.xml file (extra_info) or uses the collection name
+    Element metadata_list = (Element) GSXML.getChildByTagName(extra_info, GSXML.METADATA_ELEM+GSXML.LIST_MODIFIER);
+
+    if (metadata_list != null) {
+      
+      Element index_stem_elem = (Element) GSXML.getNamedElement(metadata_list, GSXML.METADATA_ELEM, GSXML.NAME_ATT, "indexStem");
+      
+      if (index_stem_elem != null) {
+	this.index_stem = GSXML.getNodeText(index_stem_elem);
+      }
+
+      Element infodb_type_elem = (Element) GSXML.getNamedElement(metadata_list, GSXML.METADATA_ELEM, GSXML.NAME_ATT, "infodbType");
+      if (infodb_type_elem != null) {
+	this.infodb_type = GSXML.getNodeText(infodb_type_elem);
+      }
+
+    }
+
+    if (index_stem == null || index_stem.equals("")) {
+	this.index_stem = this.cluster_name; // index_stem is the name of the db in indext/text, it is <colname>.<db>
+    }
+    if (infodb_type == null || infodb_type.equals("")) {
+      this.infodb_type = "gdbm"; // the default
+    }
+
+    /*
+    // Work out what sets this collection has.
+    //
+    // Will usually contain the collection itself, optional super
+    // collection, and maybe subcolls if appropriate classifiers are
+    // present.
+    
+    configureSetInfo();*/
+    
+    // the short_service_info is used by the message router to find the method names, 
+    /*
+    Element list_records = this.desc_doc.createElement(GSXML.SERVICE_ELEM);
+    list_records.setAttribute(GSXML.NAME_ATT, OAIXML.LIST_RECORDS);
+    list_records.setAttribute(GSXML.TYPE_ATT, "oai");
+    this.short_service_info.appendChild(list_records);
+
+    Element list_identifiers = this.desc_doc.createElement(GSXML.SERVICE_ELEM);
+    list_identifiers.setAttribute(GSXML.NAME_ATT, OAIXML.LIST_IDENTIFIERS);
+    list_identifiers.setAttribute(GSXML.TYPE_ATT, "oai");
+    this.short_service_info.appendChild(list_identifiers);
+    
+    Element list_sets = this.desc_doc.createElement(GSXML.SERVICE_ELEM);
+    list_sets.setAttribute(GSXML.NAME_ATT, OAIXML.LIST_SETS);
+    list_sets.setAttribute(GSXML.TYPE_ATT, "oai");
+    this.short_service_info.appendChild(list_sets);
+    
+    Element list_metadata_formats = this.desc_doc.createElement(GSXML.SERVICE_ELEM);
+    list_metadata_formats.setAttribute(GSXML.NAME_ATT, OAIXML.LIST_METADATA_FORMATS);
+    list_metadata_formats.setAttribute(GSXML.TYPE_ATT, "oai");
+    this.short_service_info.appendChild(list_metadata_formats);
+    */
+    Element get_record = this.desc_doc.createElement(GSXML.SERVICE_ELEM);
+    get_record.setAttribute(GSXML.NAME_ATT, IIIFXML.GET_RECORD);
+    get_record.setAttribute(GSXML.TYPE_ATT, "oai");
+    this.short_service_info.appendChild(get_record);
+    
+    return true;
+  }
+
+  public boolean configureIIIF(Element iiif_config_elem) {
+      /*      
+    this.meta_formats_definition = this.desc_doc.createElement(OAIXML.LIST_METADATA_FORMATS);
+    this.format_response_map = new HashMap<String, Element>();
+    this.format_elements_map = new HashMap<String, HashSet<String>>();
+    this.format_meta_elem_map = new HashMap<String, Element>();
+
+    // for now, all we want is the metadata prefix description and the mapping list
+    Element main_lmf_elem = (Element) GSXML.getChildByTagName(oai_config_elem, OAIXML.LIST_METADATA_FORMATS);
+    if (main_lmf_elem == null) {
+      logger.error("No listMetadataFormats element found in OAIConfig.xml");
+      return false;
+    }
+    NodeList meta_formats_list = this.config_info.getElementsByTagName(OAIXML.METADATA_FORMAT);
+    if (meta_formats_list.getLength() == 0) {
+      logger.error("no metadataFormat elements found in OAIPMH serviceRack element");
+      return false;
+    }
+      
+    boolean found_meta_format = false;
+    for(int i=0; i<meta_formats_list.getLength(); i++) {
+      Element mf = (Element) meta_formats_list.item(i);
+      String prefix = mf.getAttribute(OAIXML.METADATA_PREFIX);
+      if (prefix.equals("")) {
+	logger.error("metadataFormat element had no metadataPrefix attribute");
+	continue;
+      }
+      // get the right format from OAICOnfig
+      Element meta_format = findNamedMetadataFormat(main_lmf_elem, prefix);
+      if (meta_format == null) {
+	logger.error("Couldn't find metadataFormat named "+prefix+" in OAIConfig.xml");
+	continue;
+      }
+      
+      // copy the format definition into our stored Element
+      Element collection_version_format = (Element) this.desc_doc.importNode(meta_format, true);
+      collection_version_format.setAttribute(GSXML.NAME_ATT, prefix); // for convenience
+      this.meta_formats_definition.appendChild(collection_version_format);
+      // set up the response element for this format
+      format_response_map.put(prefix, OAIXML.getMetadataFormatShort(this.desc_doc, collection_version_format));
+      // add in collection specific mappings
+      addCollectionMappings(collection_version_format, mf);
+      // now set up a list of all collection elements for reverse lookup of the mapping
+      format_elements_map.put(prefix, getAllCollectionElements(collection_version_format));
+      format_meta_elem_map.put(prefix, OAIXML.getMetadataPrefixElement(this.desc_doc, prefix, collection_version_format));
+      
+    } // end for
+      */
+      
+	// Open the coll db db databases and store handles to them	
+    coll_db = new SimpleCollectionDatabase(infodb_type);
+    if (!coll_db.databaseOK()) {
+      logger.error("Couldn't create the collection database of type "+infodb_type);
+      return false;
+    }
+    /*
+    oaiinf_db = new SimpleCollectionDatabase(infodb_type);
+    if (!oaiinf_db.databaseOK()) {
+      logger.error("Couldn't create the oai-inf database of type "+infodb_type);
+      return false;
+    }
+    */
+    
+    // Open databases for querying
+    String coll_db_file = GSFile.collectionDatabaseFile(this.site_home, this.cluster_name, index_stem, infodb_type);
+    if (!this.coll_db.openDatabase(coll_db_file, SimpleCollectionDatabase.READ)) {
+      logger.error("Could not open collection database!");
+      return false;
+    }
+    /*
+    // the oaiinf_db is called oai-inf.<infodb_type_extension>
+    String oaiinf_db_file = GSFile.OAIInfoDatabaseFile(this.site_home, this.cluster_name, "oai-inf", infodb_type);
+    File oaiinfFile = new File(oaiinf_db_file);
+    
+    if(!oaiinfFile.exists()) {
+	logger.warn("oai-inf database for collection + " + this.cluster_name + " does not exist.");
+	oaiinf_db = null;
+    } else if (!this.oaiinf_db.openDatabase(oaiinf_db_file, SimpleCollectionDatabase.READ)) {
+	logger.warn("Could not open oai-inf database for collection + " + this.cluster_name + "!");
+	oaiinf_db = null;
+    }
+    */
+    
+    return true;
+  }
+
+    /**
+     * @return the associated OAICollection's OAI_EARLIEST_TIMESTAMP_OID record's 
+     * OAI_INF_TIMESTAMP field from the collection's oai-inf.db IN MILLISECONDS
+     */
+    /*
+    public long getEarliestTimestamp() {
+	long timestamp = -1;
+	
+	DBInfo oai_info = null;
+	if(oaiinf_db != null) {
+	    // get internal record containing the earliest timestamp of the collection
+	    oai_info = this.oaiinf_db.getInfo(OAIXML.OAI_EARLIEST_TIMESTAMP_OID);
+	    if (oai_info == null) {
+		logger.warn("Can't get collection " + this.cluster_name + "'s earliest timestamp from oai-inf db. No entry for 'OID' " + OAIXML.OAI_EARLIEST_TIMESTAMP_OID + " in the db.");		
+	    } else {
+		timestamp = Long.parseLong(oai_info.getInfo(OAIXML.OAI_INF_TIMESTAMP)) * 1000; // stored in seconds, so x1000 to  convert to milliseconds
+		//logger.info("@@@ found earliest OAI timestamp for collection " + this.coll_name + ": " + timestamp + " (ms)");
+	    }
+	}
+	return timestamp;
+    }
+    */
+
+    /*
+  protected Element findNamedMetadataFormat(Element list_meta_formats, String prefix) {
+    NodeList formats = list_meta_formats.getElementsByTagName(OAIXML.METADATA_FORMAT);
+    for (int i=0; i<formats.getLength(); i++) {
+      Element format = (Element)formats.item(i);
+      String meta_name = GSXML.getNodeText((Element)GSXML.getChildByTagName(format, OAIXML.METADATA_PREFIX));
+      if (prefix.equals(meta_name)) {
+	return format;
+      }
+    }
+    return null;
+  }
+    */
+    
+    /** goes through the mappings from the collection one, and replaces existing ones in the main one */
+    /*
+    protected void addCollectionMappings(Element main_meta_format, Element coll_meta_format) {
+
+      Element element_list = (Element)GSXML.getChildByTagName(main_meta_format, OAIXML.ELEMENT+GSXML.LIST_MODIFIER);
+      Document doc = element_list.getOwnerDocument();
+      NodeList coll_elements = coll_meta_format.getElementsByTagName(OAIXML.ELEMENT);
+      if (coll_elements.getLength()==0) {
+	// no mappings to include
+	return;
+      }
+      for (int i=0; i<coll_elements.getLength(); i++) {
+	Element e = (Element)coll_elements.item(i);
+	String elem_name = e.getAttribute(GSXML.NAME_ATT);
+	Element main_elem = GSXML.getNamedElement(element_list, OAIXML.ELEMENT, GSXML.NAME_ATT, elem_name);
+	if (main_elem == null) {
+	  logger.error(elem_name+" not found in meta format, not using it");
+	} else {
+	  element_list.replaceChild(doc.importNode(e, true),main_elem );
+      }
+      }
+    }
+    */
+    
+    /** goes through all the mappings and makes a set of all collection
+	metadata names that could become an oai meta element - acts as
+	a reverse lookup for the mappings */
+    /*
+    protected HashSet<String> getAllCollectionElements(Element meta_format) {
+      HashSet<String> meta_name_set = new HashSet<String>();
+      NodeList elements = meta_format.getElementsByTagName(OAIXML.ELEMENT);
+      for (int i=0; i<elements.getLength(); i++) {
+	Element e = (Element)elements.item(i);
+	Element map = (Element)GSXML.getChildByTagName(e, OAIXML.MAPPING);
+	if (map == null) {
+	  // there is no mapping, just use the element name
+	  meta_name_set.add(e.getAttribute(GSXML.NAME_ATT));
+	} else {
+	  String list_of_names = map.getAttribute(OAIXML.ELEMENTS);
+	  String[] name_array = list_of_names.split(",");
+	  for (int j=0; j<name_array.length; j++) {
+	    meta_name_set.add(name_array[j]);
+	  }
+	}
+      }
+      return meta_name_set;
+    }
+    */
+    
+  /** returns a specific service description */
+  public Element getServiceDescription(Document doc, String service_id, String lang, String subset) {
+
+      /*
+    if (service_id.equals(OAIXML.LIST_RECORDS)) {
+      Element list_records = doc.createElement(GSXML.SERVICE_ELEM);
+      list_records.setAttribute(GSXML.NAME_ATT, OAIXML.LIST_RECORDS);
+      list_records.setAttribute(GSXML.TYPE_ATT, "oai");
+      return list_records;
+    }
+    
+    if (service_id.equals(OAIXML.LIST_IDENTIFIERS)) {
+      Element list_identifiers = doc.createElement(GSXML.SERVICE_ELEM);
+      list_identifiers.setAttribute(GSXML.NAME_ATT, OAIXML.LIST_IDENTIFIERS);
+      list_identifiers.setAttribute(GSXML.TYPE_ATT, "oai");
+      return list_identifiers;
+    }
+    if (service_id.equals(OAIXML.LIST_SETS)) {
+      Element list_sets = doc.createElement(GSXML.SERVICE_ELEM);
+      list_sets.setAttribute(GSXML.NAME_ATT, OAIXML.LIST_SETS);
+      list_sets.setAttribute(GSXML.TYPE_ATT, "oai");
+      return list_sets;
+    }
+    if (service_id.equals(OAIXML.LIST_METADATA_FORMATS)) {
+      Element list_metadata_formats = doc.createElement(GSXML.SERVICE_ELEM);
+      list_metadata_formats.setAttribute(GSXML.NAME_ATT, OAIXML.LIST_METADATA_FORMATS);
+      list_metadata_formats.setAttribute(GSXML.TYPE_ATT, "oai");
+      return list_metadata_formats;
+    }
+      */
+      
+    if (service_id.equals(IIIFXML.GET_RECORD)) {
+      Element get_record = doc.createElement(GSXML.SERVICE_ELEM);
+      get_record.setAttribute(GSXML.NAME_ATT, IIIFXML.GET_RECORD);
+      get_record.setAttribute(GSXML.TYPE_ATT, "oai");
+      return get_record;
+    }
+    
+    return null;
+  }
+
+  /** The list sets service returns all the sets that this collection is/is part of/contains. This is gathered by Receptionist from all collections to answer the OAI ListSets request.  */
+    /*
+  protected Element processListSets(Element req) {
+    return list_sets_response;
+  }
+    */
+    
+  /** returns the actual record element used in the OAI GetRecord response */
+  protected Element processGetRecord(Element req) {
+    /** arguments:
+        identifier: required
+        metadataPrefix: required
+     *  Exceptions: badArgument; cannotDisseminateFormat; idDoesNotExist
+     */ 
+    NodeList params = GSXML.getChildrenByTagName(req, GSXML.PARAM_ELEM);
+    HashMap<String, String> param_map = GSXML.getParamMap(params);    
+    
+    String prefix = param_map.get(OAIXML.METADATA_PREFIX);
+    if (prefix == null || prefix.equals("")) {
+      //Just a double-check
+      logger.error("the value of metadataPrefix att is not present in the request.");
+      return IIIFXML.createErrorResponse(OAIXML.CANNOT_DISSEMINATE_FORMAT, "");
+    }
+    
+    // check that we support this format
+    if (!format_response_map.containsKey(prefix)) {
+      logger.error("metadata prefix is not supported for collection "+this.coll_name);
+      return IIIFXML.createErrorResponse(OAIXML.CANNOT_DISSEMINATE_FORMAT, "");
+    }
+
+    Document doc = XMLConverter.newDOM();
+    
+    String oid = param_map.get(IIIFXML.OID); // TODO should this be identifier???
+
+    /*
+    boolean OID_is_deleted = false;
+    long millis = -1;
+
+    DBInfo oai_info = null;
+    if(oaiinf_db != null) {
+	oai_info = this.oaiinf_db.getInfo(oid);
+	if (oai_info == null) {
+	    logger.warn("OID: " + oid + " is not present in the collection's oai-inf database.");
+	} else  {
+
+	    // indexdb doesn't have info on deleted docs, only oaiinf db does.
+	    // So only oaiinfdb has timestamps for deleted docs
+	    // For non-deleted doc ids: also obtain timestamp from oaiinf db, 
+	    // but if the oaiinf db doesn't exist, resort to oailastmodified fields of indexdb.
+	    String timestamp = oai_info.getInfo(OAIXML.OAI_INF_TIMESTAMP); // stored in seconds, like oailastmodified in the collection index db
+	    millis = Long.parseLong(timestamp)*1000; // in milliseconds	    
+	    
+	    String oaiinf_status = oai_info.getInfo(OAIXML.OAI_INF_STATUS);
+	    if(oaiinf_status != null && oaiinf_status.equals(OAIXML.OAI_INF_DELETED)) {
+		OID_is_deleted = true;
+	    }
+	}
+    }
+    */
+    
+    //get a DBInfo object of the identifier; if this identifier is not present in the database,
+    // null is returned.
+    DBInfo info = this.coll_db.getInfo(oid);
+    if (info == null) {
+	//if(!OID_is_deleted) { // we don't expect to find entries for deleted docs in index db. // ****
+	    logger.error("OID: " + oid + " is not present in the collection index database.");
+	    return IIIFXML.createErrorResponse(IIIFXML.ID_DOES_NOT_EXIST, "");
+	    //} // if doc deleted, id missing in indexdb is not an error: doc id would exist only in oai-inf db, marked as deleted 'D' // ****
+    }
+
+    /*
+    else if (millis == -1) { // so couldn't get doc lastmod from oaiinf db, get oailastmodified from collection's index db
+	millis = getDateStampMillis(info);	
+    }
+    String oailastmodified = (millis == -1) ? "" : OAIXML.getTime(millis);
+    */
+
+    Element get_record_response = doc.createElement(GSXML.RESPONSE_ELEM);
+    Element get_record = doc.createElement(IIIFXML.GET_RECORD);
+    get_record_response.appendChild(get_record);
+    Element record = doc.createElement(IIIFXML.RECORD);
+    //compose the header element
+    record.appendChild(createHeaderElement(doc, oid));      
+    //if(!OID_is_deleted) { // ****
+	//compose the metadata element
+	record.appendChild(createMetadataElement(doc, prefix, info));
+	// } // ****
+    get_record.appendChild(record);
+    return get_record_response;
+  }
+
+  /** return a list of records in specified set, containing metadata from specified prefix*/
+    /*
+  protected Element processListRecords(Element req) {
+    return processListIdentifiersOrRecords(req, OAIXML.LIST_RECORDS, true);
+  }
+    */
+  /** return a list of identifiers in specified set that contain metadata belonging to specified prefix. */
+    /*
+  protected Element processListIdentifiers(Element req) {
+    return processListIdentifiersOrRecords(req, OAIXML.LIST_IDENTIFIERS, false);
+  }
+    */
+    
+  // Get a list of records/identifiers that match the parameters.
+    
+  //protected Element processListIdentifiersOrRecords(Element req, String response_name, boolean include_metadata) {
+    /** arguments:
+        metadataPrefix: required
+     *  from: optional
+     *  until: optional
+     *  set: optional
+     *  resumptionToken: exclusive and optional (ignored as it has been handled by OAIReceptionist)
+     *  Exceptions: badArgument; cannotDisseminateFormat; idDoesNotExist
+     */
+    /*
+    NodeList params = GSXML.getChildrenByTagName(req, GSXML.PARAM_ELEM);
+    
+    if(params.getLength() == 0) {
+      logger.error("must at least have the metadataPrefix parameter, can't be none");
+      return OAIXML.createErrorResponse(OAIXML.BAD_ARGUMENT, "");
+    }
+    
+    HashMap<String, String> param_map = GSXML.getParamMap(params);  
+    
+    String prefix = "";
+    Date from_date = null;
+    Date until_date = null;
+    
+    if(param_map.containsKey(OAIXML.METADATA_PREFIX) == false) {    
+      //Just a double-check
+      logger.error("A param element containing the metadataPrefix is not present.");
+      return OAIXML.createErrorResponse(OAIXML.CANNOT_DISSEMINATE_FORMAT, "");
+    }
+    prefix = param_map.get(OAIXML.METADATA_PREFIX);
+    if (prefix == null || prefix.equals("")) {
+      //Just a double-check
+      logger.error("the value of metadataPrefix att is not present in the request.");
+      return OAIXML.createErrorResponse(OAIXML.CANNOT_DISSEMINATE_FORMAT, "");
+    }
+    
+    if(param_map.containsKey(OAIXML.FROM)) {
+      String from = param_map.get(OAIXML.FROM);
+      from_date = OAIXML.getDate(from);
+    }    
+    if(param_map.containsKey(OAIXML.UNTIL)) {
+      String until = param_map.get(OAIXML.UNTIL);
+      until_date = OAIXML.getDate(until);
+    }    
+
+    if (!format_response_map.containsKey(prefix)) {
+      logger.error(prefix + " metadata prefix is not supported for collection "+this.coll_name);
+      return OAIXML.createErrorResponse(OAIXML.CANNOT_DISSEMINATE_FORMAT, "");
+    }
+
+    // get list of oids
+    ArrayList<String> oid_list = null;
+    if(oaiinf_db != null) { // try getting the OIDs from the oaiinf_db
+	oid_list = new ArrayList<String>(oaiinf_db.getAllKeys());
+    
+	if(oid_list == null) { // try getting the OIDs from the oai entries in the index db
+	    logger.warn("@@@@@@@@@@@@@ NO OIDs in oai-inf db for " + this.cluster_name);
+	    oid_list = getChildrenIds(OAIXML.BROWSELIST);
+	}
+    }
+
+    if (oid_list == null) {
+      logger.error("No matched records found in collection: oai-inf and index db's browselist are empty");
+      return OAIXML.createErrorResponse(OAIXML.NO_RECORDS_MATCH, "");
+    }
+    // all validation is done
+
+    // get the list of elements that are in this metadata prefix
+    HashSet<String> set_of_elems = format_elements_map.get(prefix);
+
+    Document doc = XMLConverter.newDOM();
+    Element list_items_response = doc.createElement(GSXML.RESPONSE_ELEM);
+    Element list_items = doc.createElement(response_name);
+    list_items_response.appendChild(list_items);
+
+    for(int i=0; i<oid_list.size(); i++) {
+      String oid = oid_list.get(i);
+
+      if(oid.equals(OAIXML.OAI_EARLIEST_TIMESTAMP_OID)) { // internal id not doc id, so skip
+	  continue;
+      }
+      
+      boolean OID_is_deleted = false;
+      long millis = -1;
+
+      DBInfo oai_info = null;
+      if(oaiinf_db != null) {
+	  oai_info = this.oaiinf_db.getInfo(oid);
+	  if (oai_info == null) {
+	      logger.warn("OID: " + oid + " is not present in the collection's oai-inf database.");
+	  } else  {
+	      
+	      // indexdb doesn't have info on deleted docs, only oaiinf db does.
+	      // So only oaiinfdb has timestamps for deleted docs
+	      // For non-deleted doc ids: also obtain timestamp from oaiinf db, 
+	      // but if the oaiinf db doesn't exist, resort to oailastmodified fields of indexdb.
+	      String timestamp = oai_info.getInfo(OAIXML.OAI_INF_TIMESTAMP); // stored in seconds like oailastmodified in the collection index db	      
+	      millis = Long.parseLong(timestamp)*1000; // in milliseconds
+		  
+	      String oaiinf_status = oai_info.getInfo(OAIXML.OAI_INF_STATUS);
+	      if(oaiinf_status != null && oaiinf_status.equals(OAIXML.OAI_INF_DELETED)) {
+		  OID_is_deleted = true;
+	      }
+	  }
+      }
+      DBInfo info = this.coll_db.getInfo(oid);
+      if (info == null) { // can happen if oid was deleted, in which case only oai_info keeps a record of the oid
+	  if(!OID_is_deleted) { // we don't expect to find entries for deleted docs in index db.
+	      logger.error("Collection database does not contain information about oid: " +oid);
+	  }
+      }
+      else if (millis == -1) { // so couldn't get doc lastmod from oaiinf db, get oailastmodified from collection's index db
+	  
+	  millis = getDateStampMillis(info);
+      }
+
+      Date this_date = null;
+      String oailastmodified = (millis == -1) ? "" : OAIXML.getTime(millis);
+      
+      if (millis == -1) {
+	  if (from_date != null || until_date !=null) {
+	      continue; // if this doc doesn't have a date for some reason, and
+	      // we are doing a date range, then don't include it.
+	  }
+      } else {
+	  this_date = new Date(millis);
+	  if (from_date != null) {
+	      if(this_date.before(from_date)) {
+		  continue;
+	      }
+	  }
+	  if (until_date != null) {
+	      if (this_date.after(until_date)) {
+		  continue;
+	      }
+	  }    
+      }
+      
+      //compose the header element, which we'll be appending no matter what
+      Element header = createHeaderElement(doc, oid, oailastmodified, OID_is_deleted);
+
+      if (include_metadata) { // doing ListRecords
+	  // compose a record for adding header and metadata
+	  Element record = doc.createElement(OAIXML.RECORD);
+	  list_items.appendChild(record);
+	  //insert the header element
+	  record.appendChild(header);
+	  //Now check that this id has metadata for the required prefix.
+	  if (info != null && documentContainsMetadata(info, set_of_elems)) {
+	      // YES, it does have some metadata for this prefix	    
+	      //compose the metadata element
+	      record.appendChild(createMetadataElement(doc, prefix, info));
+	  } // otherwise the oid was 'deleted' and only in the oai-inf db and not in the info (collection index) db
+      } else { // doing ListIdentifiers
+	  //append the header element
+	  list_items.appendChild(header);      
+      }      
+      
+    }//end of for(int i=0; i<oid_list.size(); i++) of doing thru each record
+    
+    return list_items_response;        
+    
+  }
+*/
+  
+  // have implemented setDescription as an element, instead of a container containing metadata
+/*
+  private boolean configureSetInfo() {
+
+    Document doc = XMLConverter.newDOM();
+    this.list_sets_response = doc.createElement(GSXML.RESPONSE_ELEM);
+    Element list_sets_elem = doc.createElement(OAIXML.LIST_SETS);
+    this.list_sets_response.appendChild(list_sets_elem);
+    String set_name = this.coll_name;
+    String set_description = null;
+    Element name_elem = (Element)GSXML.getChildByTagName(this.config_info, OAIXML.SET_NAME);
+    if (name_elem!=null) {
+      set_name = GSXML.getNodeText(name_elem);
+      if (set_name.equals("")) {
+	set_name = this.coll_name; // default to coll name if can't find one
+      }
+    }
+    Element description_elem = (Element)GSXML.getChildByTagName(this.config_info, OAIXML.SET_DESCRIPTION);
+    if (description_elem!=null) {
+      set_description = GSXML.getNodeText(description_elem);
+      if (set_description.equals("")) {
+	set_description = null;
+      }
+    }
+    Element coll_set = OAIXML.createSet(doc, this.coll_name, set_name, set_description);
+    list_sets_elem.appendChild(coll_set);
+    
+    // are we part of any super sets?
+    NodeList super_set_list = GSXML.getChildrenByTagName(this.config_info, OAIXML.OAI_SUPER_SET);
+    for (int i=0; i<super_set_list.getLength(); i++) {
+      String super_name = ((Element)super_set_list.item(i)).getAttribute(GSXML.NAME_ATT);
+      if (super_name != null && !super_name.equals("")) {
+	list_sets_elem.appendChild(OAIXML.createSet(doc, super_name, super_name, null));
+      }
+    }
+    return true;
+  }
+*/
+    
+ /** create the metadata element used when processing ListRecords/GetRecord requests
+   */
+
+  protected Element createMetadataElement(Document doc, String prefix, DBInfo info) {
+    // the <metadata> element
+    Element metadata = doc.createElement(OAIXML.METADATA);
+    // the <oai:dc namespace...> element
+    Element prfx_str_elem = (Element)doc.importNode(this.format_meta_elem_map.get(prefix), true); 
+    metadata.appendChild(prfx_str_elem);
+    
+    Element meta_format_element = GSXML.getNamedElement(this.meta_formats_definition, OAIXML.METADATA_FORMAT, GSXML.NAME_ATT, prefix);
+    NodeList elements = meta_format_element.getElementsByTagName(OAIXML.ELEMENT);
+    // for each element in the definition
+    for (int i=0; i<elements.getLength(); i++) {
+      Element e = (Element)elements.item(i);
+      Element map = (Element)GSXML.getChildByTagName(e, OAIXML.MAPPING);
+      if (map == null) {
+	// look up the element name
+	addMetadata(prfx_str_elem, e.getAttribute(GSXML.NAME_ATT), info);
+      } else {
+	// we go though the list of names in the mapping
+	addMetadata(prfx_str_elem, e.getAttribute(GSXML.NAME_ATT), map.getAttribute(OAIXML.SELECT), map.getAttribute(OAIXML.ELEMENTS), info);
+      }
+    }
+    // output any metadata that is not just a simple mapping
+    // addCustomMetadata(prfx_str_elem, prefix, info); // ****
+    return metadata;
+  }
+
+  /** a simple addMetadata where we look for meta_name metadata, and add as that name*/
+
+  protected void addMetadata(Element meta_list_elem, String meta_name, DBInfo info) {
+    Vector<String> values = info.getMultiInfo(meta_name);
+    if (values != null && values.size()!=0) {
+      for (int i=0; i<values.size(); i++) {
+	addMetadataElement(meta_list_elem, meta_name, values.get(i));
+      }
+    }
+  }
+
+  /** more complicated addMetadata - can add multiple items. */
+
+  protected void addMetadata(Element meta_list_elem, String new_meta_name, String select_type, String name_list, DBInfo info) {
+    String[] names = name_list.split(",");
+    for (int i=0; i<names.length; i++) {
+      Vector<String> values;
+      // some special words
+      if (names[i].startsWith(OAIXML.GSF_LINK_PREFIX)) {
+	values = new Vector<String>();
+	String base_url = OAIXML.getBaseURL(); // e.g. e.g. http://host:port/greenstone3/oaiserver
+	String link_url = null;
+	if (names[i].equals(OAIXML.GSF_LINK_PREFIX+OAIXML.LINK_TYPE_DOCUMENT)) {
+	  link_url =  base_url.replace("oaiserver", "library") + "/collection/" + this.coll_name + "/document/" + info.getInfo("Identifier");
+	} else if (names[i].equals(OAIXML.GSF_LINK_PREFIX+OAIXML.LINK_TYPE_SOURCE)) {
+	  String srcfile = info.getInfo("srclinkFile");
+	  if (!srcfile.equals("")) {
+	    link_url = base_url.replace("oaiserver", "") + "sites/"
+		     + this.site_name 
+		     + "/collect/" + this.coll_name + "/index/assoc/" 
+		     + info.getInfo("assocfilepath") + "/" + srcfile;
+	  }
+	}
+	if (link_url !=null) {
+	  values.add(link_url);
+	}
+      } else {
+	values = info.getMultiInfo(names[i]);
+      }
+      if (values == null || values.size()==0) {
+	continue;
+      }
+      for (int j=0; j<values.size(); j++) {
+	addMetadataElement(meta_list_elem, new_meta_name, values.get(j));
+	if (select_type.equals(OAIXML.SELECT_SINGLE_VALUE)) {
+	  return; // only want to add one value
+	}
+      }
+      if (select_type.equals(OAIXML.SELECT_FIRST_VALID_META)) {
+	return; // we have added all values of this meta elem
+      }
+      // otherwise, we will keep going through the list and add them all.
+    }
+  }
+  
+  // specific metadata formats might need to do some custom metadata that is not
+  //just a standard mapping.
+  protected void addCustomMetadata(Element meta_list_elem, String prefix, DBInfo info) {
+    
+
+  }
+
+  /** create the actual metadata element for the list */
+
+  protected void addMetadataElement(Element meta_list_elem, String name, String value) {
+    
+    Element meta = GSXML.createTextElement(meta_list_elem.getOwnerDocument(), name, value);
+    meta_list_elem.appendChild(meta);
+  }
+
+
+  /** create a header element used when processing requests like GetRecord
+   */
+  protected Element createHeaderElement(Document doc, String oid) {
+
+        Element header = doc.createElement(OAIXML.HEADER);
+	
+	// if deleted, get the date and change oailastmodified to timestamp in oaiinfo
+	/*
+	if(deleted) {
+	    header.setAttribute(OAIXML.OAI_INF_STATUS, OAIXML.HEADER_STATUS_ATTR_DELETED); // set the header status to deleted
+		// then the timestamp for deletion will be from oai-inf database 
+	}
+	*/
+        Element identifier = doc.createElement(IIIFXML.IDENTIFIER);
+	GSXML.setNodeText(identifier, coll_name + ":" + oid);
+        header.appendChild(identifier);
+	/*
+        Element set_spec = doc.createElement(OAIXML.SET_SPEC);
+	GSXML.setNodeText(set_spec, coll_name);
+        header.appendChild(set_spec);
+        Element datestamp = doc.createElement(OAIXML.DATESTAMP);
+        GSXML.setNodeText(datestamp, oailastmodified);
+        header.appendChild(datestamp);
+	*/
+        return header;
+  }
+
+    
+  /** return the metadata information  */
+/*
+  protected Element processListMetadataFormats(Element req) {
+    // the request sent here must contain an OID. see doListMetadataFormats() in OAIReceptionist
+    Element param = GSXML.getNamedElement(req, GSXML.PARAM_ELEM, GSXML.NAME_ATT, OAIXML.OID);
+    if (param == null) {
+      logger.error("An element containing the OID attribute not is present.");
+      return OAIXML.createErrorResponse(OAIXML.ID_DOES_NOT_EXIST, "");
+    }
+    String oid = param.getAttribute(GSXML.VALUE_ATT);
+    if (oid == null || oid.equals("")) {
+      logger.error("No OID is present in the request.");
+      return OAIXML.createErrorResponse(OAIXML.ID_DOES_NOT_EXIST, "");
+    }
+*/
+    /*
+    ArrayList<String> oid_list = null;
+    if(oaiinf_db != null) { // try getting the OIDs from the oaiinf_db
+	oid_list = new ArrayList<String>(oaiinf_db.getAllKeys());
+	
+	if(oid_list == null) { // try getting the OIDs from the oai entries in the index db
+	    oid_list = getChildrenIds(OAIXML.BROWSELIST);
+	}
+    }
+    */
+/*
+    // assume meta formats are only for OIDs that have not been deleted
+    // so don't need to check oai-inf db, and can just check collection's index db for list of OIDs
+    ArrayList<String> oid_list = getChildrenIds(OAIXML.BROWSELIST);
+    if (oid_list == null || oid_list.contains(oid) == false) {
+      logger.error("OID: " + oid + " is not present in the database.");
+      Element e= OAIXML.createErrorResponse(OAIXML.ID_DOES_NOT_EXIST, "");
+//      logger.error((new XMLConverter()).getPrettyString (e));
+      return e;
+    }
+    
+    DBInfo info = null;    
+    info = this.coll_db.getInfo(oid);
+    if (info == null) { //just double check
+      return OAIXML.createErrorResponse(OAIXML.OAI_SERVICE_UNAVAILABLE, "");
+    }
+    
+    Document doc = XMLConverter.newDOM();
+    Element list_metadata_formats_response = doc.createElement(GSXML.RESPONSE_ELEM);
+    
+    Element list_metadata_formats = doc.createElement(OAIXML.LIST_METADATA_FORMATS);
+    list_metadata_formats_response.appendChild(list_metadata_formats);
+    boolean has_meta_format = false;
+    
+    // for each format in format_elements_map
+    Iterator<String> it = format_elements_map.keySet().iterator();
+    while (it.hasNext()) {
+      String format = it.next();
+      HashSet<String> set_of_elems = format_elements_map.get(format);
+      if (documentContainsMetadata(info, set_of_elems)) {
+	// add this format into the response
+	has_meta_format = true;
+	list_metadata_formats.appendChild(doc.importNode(format_response_map.get(format), true));
+      }
+    }
+
+    if (has_meta_format == false) {
+      logger.error("Specified metadata names are not contained in the database.");
+      return OAIXML.createErrorResponse(OAIXML.NO_METADATA_FORMATS, "");
+    } else {
+      return list_metadata_formats_response;
+    }
+  }
+*/
+/*
+  protected boolean documentContainsMetadata(DBInfo info, HashSet<String> set_of_elems) {
+    if (set_of_elems.size() == 0) {
+      return false;
+    }
+    Iterator<String> i = set_of_elems.iterator();
+    while (i.hasNext()) {
+      if (!info.getInfo(i.next()).equals("")) {
+	return true;
+      }
+    }
+    return false;
+  }
+*/
+
+  /** returns a list of the child ids in order, null if no children */
+/*
+  protected ArrayList<String> getChildrenIds(String node_id) {
+    DBInfo info = this.coll_db.getInfo(node_id);
+    if (info == null) {
+      return null;
+    }
+    
+    String contains = info.getInfo("contains");
+    if (contains.equals("")) {
+      return null;
+    }
+    ArrayList<String> children = new ArrayList<String>();
+    StringTokenizer st = new StringTokenizer(contains, ";");
+    while (st.hasMoreTokens()) {
+      String child_id = st.nextToken().replaceAll("\"", node_id);
+      children.add(child_id);
+    }
+    return children;    
+  }
+*/
+    
+  /**method to check whether any of the 'metadata_names' is contained in the 'info'.
+   * The name may be in the form: <name>,<mapped name>, in which the mapped name is
+   * optional. The mapped name is looked up in the DBInfo; if not present, use the first
+   * name which is mandatory.
+   */
+/*
+  protected boolean containsMetadata(DBInfo info, String[] metadata_names) {
+    if (metadata_names == null) return false;
+    logger.info("checking metadata names in db.");
+    for(int i=0; i<metadata_names.length; i++) {
+      int index = metadata_names[i].indexOf(",");
+      String meta_name = (index == -1) ? metadata_names[i] :
+                              metadata_names[i].substring(index + 1);
+      
+      if(info.getInfo(meta_name).equals("") == false) {
+        return true;
+      }
+    }
+    return false;
+  }
+*/
+/*
+  protected long getDateStampMillis(DBInfo info) {
+    // gs.OAIDateStamp is in YYYY-MM-DD
+    String time_stamp = info.getInfo(OAIXML.GS_OAI_DATE_STAMP);
+    long millis = -1;
+    if (!time_stamp.equals("")) {
+      millis = OAIXML.getTime(time_stamp);
+    }
+    if (millis == -1) {
+      // oailastmodified is in seconds
+      time_stamp = info.getInfo(OAIXML.OAI_LASTMODIFIED);
+      if (!time_stamp.equals("")) {
+	millis = Long.parseLong(time_stamp)*1000;
+      }
+    }
+    return millis;
+    
+
+  }
+*/
+}
+
+
Index: gs3-extensions/iiif-servlet/trunk/src/gsdl-src/java/org/greenstone/gsdl3/util/IIIFXML.java
===================================================================
--- gs3-extensions/iiif-servlet/trunk/src/gsdl-src/java/org/greenstone/gsdl3/util/IIIFXML.java	(revision 32842)
+++ gs3-extensions/iiif-servlet/trunk/src/gsdl-src/java/org/greenstone/gsdl3/util/IIIFXML.java	(revision 32842)
@@ -0,0 +1,764 @@
+/*
+ *    IIIFXML.java
+ *    Copyright (C) 2019 New Zealand Digital Library, http://www.nzdl.org
+ *
+ *    This program is free software; you can redistribute it and/or modify
+ *    it under the terms of the GNU General Public License as published by
+ *    the Free Software Foundation; either version 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program is distributed in the hope that it will be useful,
+ *    but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *    GNU General Public License for more details.
+ *
+ *    You should have received a copy of the GNU General Public License
+ *    along with this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+package org.greenstone.gsdl3.util;
+
+import org.greenstone.util.GlobalProperties;
+
+import org.w3c.dom.*;
+
+import java.io.*;
+import java.net.*;
+import java.util.*;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+
+// import file Logger.java
+import org.apache.log4j.*;
+
+/** these constants are used for the IIIF service */
+public class IIIFXML {
+  
+  static Logger logger = Logger.getLogger(org.greenstone.gsdl3.util.IIIFXML.class.getName());
+
+  // the leading keyword of oai protocol
+  public static final String VERB = "verb";
+
+  // Possible states for non-IIIF/non-verb activate/deactivate requests  
+  public static final int DEACTIVATION = 0;
+  public static final int ACTIVATION = 1;
+  
+  // six valid oai verbs
+  public static final String GET_RECORD = "GetRecord";
+    /*    
+  public static final String LIST_RECORDS = "ListRecords";
+  public static final String LIST_IDENTIFIERS = "ListIdentifiers";
+  public static final String LIST_SETS = "ListSets";
+  public static final String LIST_METADATA_FORMATS = "ListMetadataFormats";
+  public static final String IDENTIFY = "Identify";
+    */
+
+    
+  // oai request parameters
+  public static final String IDENTIFIER = "identifier";
+  /*
+  public static final String METADATA_PREFIX = "metadataPrefix";
+  public static final String FROM = "from";
+  public static final String UNTIL = "until";
+  public static final String SET = "set";
+  public static final String RESUMPTION_TOKEN = "resumptionToken";
+  public static final String IDENTIFIER = "identifier";
+    */
+    
+  // Error element and code att
+  public static final String ERROR = "error";
+  public static final String CODE = "code";
+  
+  // OAI error codes
+      public static final String ID_DOES_NOT_EXIST = "idDoesNotExist";
+    /*
+  public static final String BAD_ARGUMENT = "badArgument";
+  public static final String BAD_RESUMPTION_TOKEN = "badResumptionToken";
+  public static final String BAD_VERB = "badVerb";
+  public static final String CANNOT_DISSEMINATE_FORMAT = "cannotDisseminateFormat";
+  public static final String NO_METADATA_FORMATS = "noMetadataFormats";
+  public static final String NO_RECORDS_MATCH = "noRecordsMatch";
+  public static final String NO_SET_HIERARCHY = "noSetHierarchy";
+    */
+
+  // words used to compose oai responses
+  // many of these used in OAIConfig too
+
+  // General
+    public static final String IIIF_PMH = "IIIF-PMH"; // ****
+  public static final String RESPONSE_DATE = "responseDate";
+  public static final String REQUEST = "request";
+
+  // internal "OID" identifiers in the oai-inf db
+  /** represents the timestamp of the OAI collection (when its oai-inf db was first created) */
+    /*
+  public static final String OAI_EARLIEST_TIMESTAMP_OID = "_earliesttimestamp";  
+    */
+    
+  // Identify data
+    // OAI replacing => IIIF, maybe want baseURL and oai-identifier
+      public static final String BASE_URL = "baseURL";
+    /*
+  public static final String ADMIN_EMAIL = "adminEmail";
+  public static final String BASE_URL = "baseURL";
+  public static final String COMPRESSION = "compression";
+  public static final String DELETED_RECORD = "deletedRecord";
+  public static final String DESCRIPTION = "description";
+  public static final String EARLIEST_DATESTAMP = "earliestDatestamp"; // taken from buildconfig used as publishing date by RSS service
+  public static final String EARLIEST_OAI_DATESTAMP = "earliestOAIDatestamp"; // earliest timestamp of an OAI collection stored in oai-inf db
+  public static final String GRANULARITY = "granularity";
+  public static final String LAST_MODIFIED = "lastmodified";
+  public static final String PROTOCOL_VERSION = "protocolVersion";
+  public static final String REPOSITORY_NAME = "repositoryName";
+  public static final String OAI_IDENTIFIER = "oai-identifier";
+  public static final String SCHEME = "scheme";
+  public static final String REPOSITORY_IDENTIFIER = "repositoryIdentifier";
+  public static final String DELIMITER = "delimiter";
+  public static final String SAMPLE_IDENTIFIER = "sampleIdentifier";
+    */
+
+    /*
+  // metadata formats
+  public static final String METADATA_FORMAT = "metadataFormat";
+  public static final String SCHEMA = "schema";
+  public static final String METADATA_NAMESPACE = "metadataNamespace";
+  public static final String META_FORMAT_DC = "oai_dc";
+    */
+
+    
+  // record response data
+  // SET_SPEC
+  public static final String RECORD = "record";
+    /*
+  public static final String HEADER = "header";
+  public static final String DATESTAMP = "datestamp";
+  public static final String METADATA = "metadata";
+    */
+
+    /*
+  // list sets
+  // SET, 
+  public static final String SET_NAME = "setName";
+  public static final String SET_SPEC = "setSpec";
+  public static final String SET_DESCRIPTION = "setDescription";
+    */
+
+    /*
+  // resumption token element
+  public static final String RESUMPTION_TOKEN_ELEM = "resumptionToken";
+  public static final String EXPIRATION_DATE = "expirationDate";
+  public static final String COMPLETE_LIST_SIZE = "completeListSize";
+  public static final String CURSOR = "cursor";
+    */
+    
+  // extra elements/attributes from OAIConfig
+    /*
+  public static final String OAI_INFO = "oaiInfo";
+  public static final String USE_STYLESHEET = "useOAIStylesheet";
+  public static final String STYLESHEET = "OAIStylesheet";
+  public static final String RESUME_AFTER = "resumeAfter";
+  public static final String RESUMPTION_TOKEN_EXPIRATION = "resumptionTokenExpiration";
+  public static final String OAI_SUPER_SET = "oaiSuperSet";
+  public static final String ELEMENT = "element";
+  public static final String ELEMENTS = "elements";
+  public static final String MAPPING = "mapping";
+  public static final String SELECT = "select";
+  public static final String SELECT_SINGLE_VALUE = "firstvalue";
+  public static final String SELECT_FIRST_VALID_META = "firstvalidmetadata";
+  public static final String SELECT_ALL_VALUES = "allvalues";
+  // code constants
+  public static final String GS_OAI_DATE_STAMP = "gs.OAIDateStamp";
+   public static final String GS_OAI_RESOURCE_URL = "gs.OAIResourceURL";
+  public static final String GSF_LINK_PREFIX = "gsflink.";
+  public static final String LINK_TYPE_DOCUMENT = "document";
+  public static final String LINK_TYPE_SOURCE = "source";
+   public static final String ILLEGAL_OAI_VERB = "Illegal OAI verb";
+   public static final String LASTMODIFIED = "lastmodified";
+  // // The node id in the collection database, which contains all the OIDs in the database
+   public static final String BROWSELIST = "browselist";
+   public static final String OAI_LASTMODIFIED = "oailastmodified";
+    */
+   public static final String OAIPMH = "OAIPMH";
+    
+    //public static final String OAI_SET_LIST = "oaiSetList"; // ****
+   public static final String OAI_SERVICE_UNAVAILABLE = "OAI service unavailable";
+   public static final String OID = "OID";
+    /*
+    // The node id in the oai-inf database of the collection, which should contain all the OIDs in the db
+    public static final String OAI_INF_STATUS = "status"; // D = Deleted; E = Existing (PD = Provisionally Deleted but PD should not be present at this stage in the oai-inf database)
+    public static final String OAI_INF_TIMESTAMP = "timestamp"; // the time of deletion/last (re)indexing of doc
+    public static final String OAI_INF_DATESTAMP = "datestamp"; // date version of timestamp    
+    // oai-inf db values for OAI_INF_STATUS
+    public static final String OAI_INF_DELETED = "D";
+    public static final String OAI_INF_EXISTS = "E";
+    public static final String OAI_INF_PROVISIONALLY_DELETED = "PD";
+    // header values for ListRecords/GetRecord and listIdentifiers
+    public static final String HEADER_STATUS_ATTR_DELETED = "deleted";
+    */
+    
+  public static final String IIIF_SERVICE_RACK = "IIIFPMH";
+  //system-dependent file separator, maybe '/' or '\'
+  public static final String FILE_SEPARATOR = File.separator;
+    //public static final String OAI_VERSION1 = "1.0";
+  public static final String IIIF_VERSION2 = "2.1";
+  /*************************above are final values****************************/
+    
+    
+  //initialized in getIIIFConfigXML()
+  public static Element iiif_config_elem = null;
+    
+  //stores the date format "yyyy-MM-ddTHH:mm:ssZ"
+  // this is the granularity for datestamps
+    /*
+  public static String granularity = "";
+    */
+    
+  // http://www.openarchives.org/OAI/openarchivesprotocol.html#DatestampsRequests 
+  // specifies that all repositories must support YYYY-MM-DD (yyyy-MM-dd in Java)
+  // this would be in addition to the other (optional) granularity of above that 
+  // a repository may additionally choose to support.
+    /*
+  public static final String default_granularity = "yyyy-MM-dd";
+    */
+    
+    //public static long token_expiration = 7200;
+  /** which version of oai that this oaiserver supports; default is 2.0 
+   *  initialized in getOAIConfigXML()
+   */
+  public static final String iiif_version = "2.1";
+  public static String baseURL = "";
+    
+  /** Converter for parsing files and creating Elements */
+  public static XMLConverter converter = new XMLConverter();
+  
+  public static String[] special_char = {"/", "?", "#", "=", "&", ":", ";", " ", "%", "+"};
+  public static String[] escape_sequence = {"%2F", "%3F", "%23", "%3D", "%26", "%3A", "%3B", "%20", "%25", "%2B"};
+
+  public static String getIIIFVersion() {
+    return iiif_version;
+  }
+	
+  public static String getBaseURL() {
+    return baseURL;
+  }
+	
+  /** Read in IIIFConfig.xml (residing web/WEB-INF/classes/) and use it to configure the receptionist etc.
+   *  the baseURL variables are also set in here. 
+   *  The init() method is also called in here. */ 
+  public static Element getIIIFConfigXML() {
+     
+    File iiif_config_file = null;
+
+    try {
+      URL iiif_config_url = Class.forName("org.greenstone.gsdl3.IIIFServerBridge").getClassLoader().getResource("IIIFConfig.xml");
+      if (iiif_config_url == null) {
+	logger.error("couldn't find IIIFConfig.xml via class loader");
+	return null;
+      }
+      iiif_config_file = new File(iiif_config_url.toURI());
+      if (!iiif_config_file.exists()) {
+        logger.error(" IIIF config file: "+iiif_config_file.getPath()+" not found!");
+        return null;
+      }
+    } catch(Exception e) {
+      logger.error("couldn't find IIIFConfig.xml "+e.getMessage());
+      return null;
+    }
+
+    Document iiif_config_doc = converter.getDOM(iiif_config_file, "utf-8");
+    if (iiif_config_doc != null) {
+      iiif_config_elem = iiif_config_doc.getDocumentElement();
+    } else {
+      logger.error("Failed to parse IIIF config file IIIFConfig.xml.");
+      return null;
+    }
+      
+    //initialize oai_version
+    /*
+    Element protocol_version = (Element)GSXML.getChildByTagName(iiif_config_elem, PROTOCOL_VERSION);
+    iiif_version = GSXML.getNodeText(protocol_version).trim();
+    */
+    
+    // initialize baseURL
+    Element base_url_elem = (Element)GSXML.getChildByTagName(iiif_config_elem, BASE_URL);
+    baseURL = GSXML.getNodeText(base_url_elem);
+
+    /*
+    //initialize token_expiration
+    Element expiration = (Element)GSXML.getChildByTagName(iiif_config_elem, RESUMPTION_TOKEN_EXPIRATION);
+    String expire_str = GSXML.getNodeText(expiration).trim();
+    if (expiration != null && !expire_str.equals("")) {
+      token_expiration = Long.parseLong(expire_str);
+    }
+    */
+    /*
+    // read granularity from the config file
+    Element granu_elem = (Element)GSXML.getChildByTagName(iiif_config_elem, GRANULARITY);
+    //initialize the granu_str which might be used by other methods (eg, getDate())
+    granularity = GSXML.getNodeText(granu_elem).trim();
+
+    //change "yyyy-MM-ddTHH:mm:ssZ" to "yyyy-MM-dd'T'HH:mm:ss'Z'"
+    granularity = granularity.replaceAll("T", "'T'");
+    granularity = granularity.replaceAll("Z", "'Z'");
+    granularity = granularity.replaceAll("YYYY", "yyyy").replaceAll("DD", "dd").replaceAll("hh", "HH");
+    */
+    
+    return iiif_config_elem;
+  }
+
+    /*
+  public static String[] getMetadataMapping(Element metadata_format) {
+
+    if (metadata_format == null) {
+      return null;
+    }
+    NodeList mappings = metadata_format.getElementsByTagName(MAPPING);
+    int size = mappings.getLength();
+    if (size == 0) {
+      logger.info("No metadata mappings are provided in IIIFConfig.xml."); 
+      return null;
+    }
+    String[] names = new String[size];
+    for (int i=0; i<size; i++) {
+      names[i] = GSXML.getNodeText((Element)mappings.item(i)).trim();
+    }
+    return names;      
+    
+  }
+    
+  public static String[] getGlobalMetadataMapping(String prefix) {
+    Element list_meta_formats = (Element)GSXML.getChildByTagName(iiif_config_elem, LIST_METADATA_FORMATS);
+    if(list_meta_formats == null) {
+      return null;
+    }
+    Element metadata_format = GSXML.getNamedElement(list_meta_formats, METADATA_FORMAT, METADATA_PREFIX, prefix);
+    if(metadata_format == null) {
+      return null;
+    }
+    return getMetadataMapping(metadata_format);
+  }
+    */
+    
+  /** Copies out the main info from a metadataFormat element, leaving behind the mapping stuff. This gets the bit needed for OAI response */
+    /*
+  public static Element getMetadataFormatShort(Document doc, Element meta_format_long) {
+    Element meta_fmt = doc.createElement(OAIXML.METADATA_FORMAT);
+    // Copy in the elements that we want, and ignore the rest
+    meta_fmt.appendChild(doc.importNode(GSXML.getChildByTagName(meta_format_long, OAIXML.METADATA_PREFIX), true));
+	meta_fmt.appendChild(doc.importNode(GSXML.getChildByTagName(meta_format_long, OAIXML.SCHEMA), true));
+	meta_fmt.appendChild(doc.importNode(GSXML.getChildByTagName(meta_format_long, OAIXML.METADATA_NAMESPACE), true));
+
+	return meta_fmt;
+  }
+    */
+
+    /*
+  public static long getTokenExpiration() {
+    return token_expiration*1000; // in milliseconds
+  }
+    */
+    
+  /** TODO: returns a basic response for appropriate oai version
+   *  
+   */
+  public static  Element createBasicResponse(Document doc, String verb, String[] pairs) {
+
+    Element response = createResponseHeader(doc, verb);
+      
+    //set the responseDate and request elements accordingly
+    Element request_elem = (Element)GSXML.getChildByTagName(response, REQUEST);
+    if (verb.equals("")) {
+      request_elem.setAttribute(VERB, verb);
+    }
+    int num_pairs = (pairs==null)? 0 : pairs.length;
+    for (int i=num_pairs - 1; i>=0; i--) {
+      int index = pairs[i].indexOf("=");
+      if (index != -1) {
+	String[] strs = pairs[i].split("=");
+	if(strs != null && strs.length == 2) {
+	  request_elem.setAttribute(strs[0], oaiDecode(strs[1]));
+	}
+      }
+    }//end of for()
+      
+    GSXML.setNodeText(request_elem, baseURL);
+      
+    Node resp_date = GSXML.getChildByTagName(response, RESPONSE_DATE); // ****
+    /*
+    if (resp_date != null) {
+      GSXML.setNodeText((Element)resp_date, getCurrentUTCTime());
+    }
+    */
+    
+    return response;
+  }
+  /** @param error_code the value of the code attribute
+   *  @param error_text the node text of the error element
+   *  @return an iiif error <message><response><error>
+   */
+  public static Element createErrorMessage(String error_code, String error_text) {
+    Document doc = converter.newDOM();
+    Element message = doc.createElement(GSXML.MESSAGE_ELEM);
+    Element resp = doc.createElement(GSXML.RESPONSE_ELEM);
+    message.appendChild(resp);
+    Element error = createErrorElement(doc, error_code, error_text);
+    resp.appendChild(error);
+    return message;
+  }
+    
+  /** @param error_code the value of the code attribute
+   *  @param error_text the node text of the error element
+   *  @return an iiif error <response><error>
+   */
+  public static Element createErrorResponse(String error_code, String error_text) {
+    Document doc = converter.newDOM();
+    Element resp = doc.createElement(GSXML.RESPONSE_ELEM);
+    Element error = createErrorElement(doc, error_code, error_text);
+    resp.appendChild(error);
+    return resp;
+  }
+    
+  /** @param error_code the value of the code attribute
+   *  @param error_text the node text of the error element
+   *  @return an iiif error <error>
+   */
+  public static Element createErrorElement(Document doc, String error_code, String error_text) {
+    Element error = doc.createElement(ERROR);
+    error.setAttribute(CODE, error_code);
+    GSXML.setNodeText(error, error_text);
+    return error;
+  }
+
+  // This is the response message sent when there's a request to activate/deactivate a non-OAI collection
+  // A request to activate a non-existent/non-OAI collection is not invalid, it's just that we won't process it.
+  // So we still return status code OK (OK status code is needed for servercontrol.pm of activate.pl to recognise
+  // that the command had been "successful" when it runs de/activate).
+  public static Element createDeActivationOfNonIIIFCollResponse(int activationState, String collname) {
+	Document doc = converter.newDOM();
+    Element response = doc.createElement(GSXML.RESPONSE_ELEM);
+	response.setAttribute("status", "OK"); 
+	String message = "collection: " + collname + " is not enabled for IIIF.";
+	if(activationState == ACTIVATION) {
+		message += " Not attempting to activate it.";
+	  } else {
+		message += " Not attempting to deactivate it.";
+	  }
+	  GSXML.setNodeText(response, message);
+	  return response;
+  }
+  
+  // The response message sent when a request comes in to activate/deactivate a proper IIIF collection.
+   public static Element createActivationStateResponse(boolean success, int activationState, String collname) {
+    Document doc = converter.newDOM();
+    Element response = doc.createElement(GSXML.RESPONSE_ELEM);
+    if (success) {
+      response.setAttribute("status", "OK");
+	  if(activationState == ACTIVATION) {
+		GSXML.setNodeText(response, "collection: " + collname + " activated");
+	  } else {
+		GSXML.setNodeText(response, "collection: " + collname + " deactivated");  
+	  }
+    } else {
+      response.setAttribute("status", "FAIL");
+	  if(activationState == ACTIVATION) {
+		GSXML.setNodeText(response, "Failed to activate collection " + collname);
+	  } else {
+		GSXML.setNodeText(response, "Failed to deactivate collection " + collname);
+	  }
+    }
+    return response;
+  }
+  
+  public static Element createResetResponse(boolean success) {
+    Document doc = converter.newDOM();
+    Element response = doc.createElement(GSXML.RESPONSE_ELEM);
+    if (success) {
+      response.setAttribute("status", "OK");
+      GSXML.setNodeText(response, "Reset IIIFServerBridge successfully");
+    } else {
+      response.setAttribute("status", "FAIL");
+      GSXML.setNodeText(response, "Failed to reset IIIFServerBridge");
+    }
+    return response;
+  }
+  /** convert the escaped sequences (eg, '%3A') of those special characters back to their
+   *  original form (eg, ':'). 
+   */
+  public static String oaiDecode(String escaped_str) {
+    logger.info("oaiDecode() " +escaped_str);
+    for (int i=0; i<special_char.length; i++) {
+      if (escaped_str.indexOf(escape_sequence[i]) != -1) {
+	escaped_str = escaped_str.replaceAll(escape_sequence[i], special_char[i]);
+      }
+    }
+    return escaped_str;        
+  }
+  /** convert those special characters (eg, ':') to their
+   *  escaped sequences (eg, '%3A'). 
+   */
+  public static String oaiEncode(String original_str) {
+    logger.info("oaiEncode() " + original_str);      
+    for (int i=0; i<special_char.length; i++) {
+      if (original_str.indexOf(special_char[i]) != -1) {
+	original_str = original_str.replaceAll(special_char[i], escape_sequence[i]);
+      }
+    }
+    return original_str;  
+  }
+  /** convert YYYY-MM_DDThh:mm:ssZ to yyyy-MM-ddTHH:mm:ssZ
+   */
+    /*
+  public static String convertToJava(String oai_format) {
+    oai_format = oai_format.replaceAll("YYYY", "yyyy").replaceAll("DD", "dd").replaceAll("hh", "HH");
+    return oai_format;
+  }
+    */
+  /** convert yyyy-MM-ddTHH:mm:ssZ to YYYY-MM_DDThh:mm:ssZ
+   */
+    /*
+  public static String convertToOAI(String java_format) {
+    java_format = java_format.replaceAll("yyyy", "YYYY").replaceAll("dd", "DD").replaceAll("HH", "hh");
+    return java_format;      
+  }    
+  public static String getCurrentUTCTime() {
+    Date current_utc = new Date(System.currentTimeMillis());
+    //granularity is in the form: yyyy-MM-dd'T'HH:mm:ss'Z '
+    DateFormat formatter = new SimpleDateFormat(granularity);
+    return formatter.format(current_utc);
+  }
+    */
+  /** get a Date object from a Date format pattern string
+   *
+   * @param pattern - in the form: 2007-06-14T16:48:25Z, for example.
+   * @return a Date object - null if the pattern is not in the specified form
+   */
+    /*
+  public static Date getDate(String pattern) {
+    if (pattern == null || pattern.equals("")) {
+      return null;
+    }
+    Date date = null;
+    //      String str = pattern.replaceAll("T", " ");
+    //      str = str.replaceAll("Z", "");
+    SimpleDateFormat sdf = null;
+    try {
+      sdf = new SimpleDateFormat(granularity);
+      date = sdf.parse(pattern);
+    } catch(Exception e) {
+      if(!default_granularity.equals(granularity)) { // try validating against default granularity
+	try {
+	  date = null;
+	  sdf = null;
+	  sdf = new SimpleDateFormat(default_granularity);
+	  date = sdf.parse(pattern);
+	} catch(Exception ex) {
+	  logger.error("invalid date format: " + pattern);
+	  return null;
+	}
+      } else {
+	logger.error("invalid date format: " + pattern);
+	return null;
+      }
+    }
+    return date;
+  }
+    */
+  /** get the million second value from a string representing time in a pattern
+   * (eg, 2007-06-14T16:48:25Z)
+   */
+    /*
+  public static long getTime(String pattern) {
+    if (pattern == null || pattern.equals("")) {
+      return -1;
+    }
+    Date date = null;
+    SimpleDateFormat sdf = null;
+    try {
+      //granularity is a global variable in the form: yyyy-MM-ddTHH:mm:ssZ 
+      sdf = new SimpleDateFormat(granularity);
+      date = sdf.parse(pattern);
+    } catch(Exception e) {
+      if(!default_granularity.equals(granularity)) { // try validating against default granularity
+	try {
+	  date = null;
+	  sdf = null;
+	  sdf = new SimpleDateFormat(default_granularity);
+	  date = sdf.parse(pattern);
+	} catch(Exception ex) {
+	  logger.error("invalid date format: " + pattern);
+	  return -1;
+	}
+      } else {
+	logger.error("invalid date format: " + pattern);
+	return -1;
+      }        
+    }
+    return date.getTime();
+  }   
+    */ 
+  /** get the string representation of a time from a long value(long type)
+   */
+    /*
+  public static String getTime(long milliseconds) {
+    Date date = new Date(milliseconds);
+    SimpleDateFormat sdf = new SimpleDateFormat(granularity);
+    return sdf.format(date);
+  }   
+    */ 
+  public static Element createResponseHeader(Document response_doc, String verb) {
+      String tag_name = (iiif_version.equals(IIIF_VERSION2))? IIIF_PMH : verb; // ****
+    Element oai = response_doc.createElement(tag_name);
+    Element resp_date = response_doc.createElement(RESPONSE_DATE);
+    Element req = response_doc.createElement(REQUEST);
+    oai.appendChild(resp_date);
+    oai.appendChild(req);
+
+    if(iiif_version.equals(IIIF_VERSION2)) { // ****
+      oai.setAttribute("xmlns", "http://www.openarchives.org/OAI/2.0/");
+      oai.setAttribute("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance");
+      oai.setAttribute("xsi:schemaLocation", "http://www.openarchives.org/OAI/2.0/ \n http://www.openarchives.org/OAI/2.0/OAI-PMH.xsd");
+    } else {
+      oai.setAttribute("xmlns", "http://www.openarchives.com/OAI/1.1/OAI_" + verb);
+      oai.setAttribute("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance");
+      oai.setAttribute("xsi:schemaLocation", "http://www.openarchives.org/OAI/1.1/OAI_" + verb + "\n http://www.openarchives.org/OAI/1.1/OAI_" + verb + ".xsd");
+    }
+    return oai;
+  }
+
+    /*
+  public static Element getMetadataPrefixElement(Document doc, String prefix, Element meta_format) {
+
+    Element ns_elem = (Element)GSXML.getChildByTagName(meta_format, METADATA_NAMESPACE);
+    String namespace = null;
+    if (ns_elem != null) {
+      namespace = GSXML.getNodeText(ns_elem);
+    }
+    if (namespace == null || namespace.equals("")) {
+      logger.error("No namespace URI found in metadataFormat elemnt for "+prefix);
+      logger.error(XMLConverter.getPrettyString(meta_format));
+      return null;
+    }
+
+    Element sc_elem = (Element)GSXML.getChildByTagName(meta_format, SCHEMA);
+    String schema = null;
+    if (sc_elem != null) {
+      schema = GSXML.getNodeText(sc_elem);
+    }
+    if (schema == null || schema.equals("")) {
+      logger.error("No schema found in metadataFormat element for "+prefix);
+      logger.error(XMLConverter.getPrettyString(meta_format));
+      return null;
+    }
+      
+    String tag_name = getMetadataTagName(prefix, oai_version);
+    Element oai = doc.createElement(tag_name);
+    oai.setAttribute("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance");
+    
+    if (oai_version.equals(OAI_VERSION2)) {
+      oai.setAttribute("xmlns:"+prefix, namespace);
+      if (prefix.equals(META_FORMAT_DC)) {
+	// there seems to be an extra one for dc
+	oai.setAttribute("xmlns:dc", "http://purl.org/dc/elements/1.1/");
+      }
+      oai.setAttribute("xsi:schemaLocation", namespace+" \n "+schema);
+    } else {
+      oai.setAttribute("xmlns", "http://www.openarchives.com/OAI/1.1/");
+      if (prefix.equals(META_FORMAT_DC)) {
+	oai.setAttribute("xsi:schemaLocation", "http://www.openarchives.org/OAI/1.1/" + tag_name + ".xsd");
+      } else {
+	oai.setAttribute("xsi:schemaLocation", schema);
+      }
+    }
+
+    return oai;
+    
+  }
+    
+  public static String getMetadataTagName(String prefix, String oai_version) {
+    if (prefix.equals(META_FORMAT_DC)) {
+      if (oai_version.equals(OAI_VERSION2)) {
+	return "oai_dc:dc";
+      }
+      return "dc";
+    }
+    return prefix;
+  }
+
+  public static HashMap<String, Node> getChildrenMapByTagName(Node n, String tag_name) {
+	
+    HashMap<String, Node> map= new HashMap<String, Node>();
+    Node child = n.getFirstChild();
+    while (child!=null) {
+      String name = child.getNodeName();
+      if(name.equals(tag_name)) {
+	map.put(name, child);
+      }
+      child = child.getNextSibling();
+    }
+    return map;
+  }
+    */
+    
+  public static Element createOAIIdentifierXML(Document doc, String repository_id, String sample_collection, String sample_doc_id) {
+    String xml = "<oai-identifier xmlns=\"http://www.openarchives.org/OAI/2.0/oai-identifier\"\n xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n xsi:schemaLocation=\"http://www.openarchives.org/OAI/2.0/oai-identifier\n http://www.openarchives.org/OAI/2.0/oai-identifier.xsd\">\n <scheme>oai</scheme>\n<repositoryIdentifier>" + repository_id + "</repositoryIdentifier>\n<delimiter>:</delimiter>\n<sampleIdentifier>oai:"+repository_id+":"+sample_collection+":"+sample_doc_id+"</sampleIdentifier>\n</oai-identifier>";
+
+    Document xml_doc = converter.getDOM(xml);
+    return (Element)doc.importNode(xml_doc.getDocumentElement(), true);
+    
+
+  }
+
+  public static Element createGSDLElement(Document doc) {
+    String xml = "<gsdl xmlns=\"http://www.greenstone.org/namespace/gsdl_oaiinfo/1.0/gsdl_oaiinfo\"\n xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n xsi:schemaLocation=\"http://www.greenstone.org/namespace/gsdl_oaiinfo/1.0/gsdl_oaiinfo\n   http://www.greenstone.org/namespace/gsdl_oaiinfo/1.0/gsdl_oaiinfo.xsd\"></gsdl>";
+    Document xml_doc = converter.getDOM(xml);
+    return (Element)doc.importNode(xml_doc.getDocumentElement(), true);
+    
+
+  }
+    /*
+  public static Element createSet(Document doc, String spec, String name, String description) {
+
+    Element set_elem = doc.createElement(SET);
+    Element set_spec = doc.createElement(SET_SPEC);
+    GSXML.setNodeText(set_spec, spec);
+    set_elem.appendChild(set_spec);
+    Element set_name = doc.createElement(SET_NAME);
+    GSXML.setNodeText(set_name, name);
+    set_elem.appendChild(set_name);
+    if (description != null) {
+      Element set_description = doc.createElement(SET_DESCRIPTION);
+      GSXML.setNodeText(set_description, description);
+      set_elem.appendChild(set_description);
+    }
+    return set_elem;
+    
+  }
+    */
+    
+  /** returns the resumptionToken element to go into an OAI response */
+    /*
+  public static Element createResumptionTokenElement(Document doc, String token_name, int total_size, int cursor, long expiration_time) {
+    Element token = doc.createElement(OAIXML.RESUMPTION_TOKEN);
+    if (total_size != -1) {
+      token.setAttribute(OAIXML.COMPLETE_LIST_SIZE, "" + total_size);
+    }
+    if (cursor != -1) {
+      token.setAttribute(OAIXML.CURSOR, "" + cursor);
+    }
+    if(expiration_time !=-1) {
+      token.setAttribute(OAIXML.EXPIRATION_DATE, getTime(expiration_time));
+    }
+   
+    if (token != null) {
+      GSXML.setNodeText(token, token_name);
+    }
+    return token;
+  }
+    */
+}
+
+
+
+
+
+
