/* See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * Esri Inc. licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.esri.gpt.framework.search;

import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

import com.esri.gpt.catalog.search.SearchCriteria;
import com.esri.gpt.catalog.search.SearchEngineCSW;
import com.esri.gpt.catalog.search.SearchException;
import com.esri.gpt.framework.geometry.Envelope;
import com.esri.gpt.framework.http.HttpClientRequest;
import com.esri.gpt.framework.request.Criteria;
import com.esri.gpt.framework.request.QueryResult;
import com.esri.gpt.framework.request.Record;
import com.esri.gpt.framework.request.Records;
import com.esri.gpt.framework.util.Val;
import com.esri.gpt.framework.xml.XsltTemplate;
import com.esri.gpt.server.csw.client.CswProfile;
import com.esri.gpt.server.csw.client.CswRecord;
import com.esri.gpt.server.csw.client.CswRuntimeException;
import com.esri.gpt.server.csw.client.CswSearchCriteria;
import com.esri.gpt.server.csw.client.InvalidOperationException;
import com.esri.gpt.server.csw.client.NullReferenceException;
import com.esri.gpt.server.csw.client.Utils;


/**
 * Search profile.
 * @param <C> criteria type
 * @param <RD> record type
 * @param <RS> collection of records type
 * @param <QR> query result type
 */
public abstract class SearchXslProfile<C extends Criteria, RD extends Record,
  RS extends Records<RD>, QR extends QueryResult<RS>> {

// class variables =============================================================
/** The class logger *. */
private static Logger LOG = Logger.getLogger(CswProfile.class
    .getCanonicalName());

private final static String SCHEME_METADATA_DOCUMENT = 
  "urn:x-esri:specification:ServiceType:ArcIMS:Metadata:Document";

/**
 * Type of search output format.
 */
public static enum FORMAT_SEARCH_TO_XSL {MINIMAL_LEGACY_CSWCLIENT, 
  FULL_NATIVE_GPTXML, DETAILED_GPT_CSW202};

private static final String XSL_PARAM_HITS_ONLY = "searchQueryDoHitsOnly";
// instance variables ==========================================================
/** The description. */
private String description;

/** The filter_extentsearch. */ 
private boolean filter_extentsearch;

/** The filter_livedatamap. */
private boolean filter_livedatamap;

/** indicates whether endpoint is harvestable */
private boolean harvestable = true;

/** The id. */
private String id;

/** The kvp. */
private String kvp;

/** The metadataxslt. */
private String metadataxslt;

/** The metadata xslt obj. */
private XsltTemplate metadataXsltObj; 

/** The name. */
private String name;

/** The requestxslt. */
private String requestxslt;

/** The request xslt obj. */
private XsltTemplate requestXsltObj; 

/** The responsexslt. */
private String responsexslt;

/** The response xslt obj. */
private XsltTemplate responseXsltObj; 

/** XSLT factory*. */
private TransformerFactory factory;

/** The supports spatial query. */
private boolean supportsSpatialQuery;

/** The supports content type query. */
private boolean supportsContentTypeQuery;

/** The supports spatial boundary. */
private boolean supportsSpatialBoundary;

//TODO: Put this in the profile, find out which profiles accept this
private boolean supportsFullMetadataAtSearch;

/** The format request to xsl. */
private FORMAT_SEARCH_TO_XSL formatRequestToXsl;



// constructors ================================================================
/**
 * Instantiates a new csw profile.
 */
public SearchXslProfile() {
}

/**
 * The Constructor.
 * 
 * @param sid
 *          the sid
 * @param sname
 *          the sname
 * @param sdescription
 *          the sdescription
 */
public SearchXslProfile(String sid, String sname, String sdescription) {
}

/**
 * The Constructor.
 * 
 * @param livedatamap
 *          the livedatamap
 * @param extentsearch
 *          the extentsearch
 * @param id
 *          the id
 * @param name
 *          the name
 * @param description
 *          the description
 * @param kvp
 *          the kvp
 * @param requestxslt
 *          the requestxslt
 * @param responsexslt
 *          the responsexslt
 * @param metadataxslt
 *          the metadataxslt
 */
public SearchXslProfile(String id, String name, String description, String kvp,
    String requestxslt, String responsexslt, String metadataxslt,
    boolean livedatamap, boolean extentsearch) {
  this.id = id;
  this.name = name;
  this.description = description;
  this.kvp = kvp;
  this.requestxslt = requestxslt;
  this.responsexslt = responsexslt;
  this.metadataxslt = metadataxslt;
  this.filter_livedatamap = livedatamap;
  this.filter_extentsearch = extentsearch;

}

// properties ==================================================================

/**
 * Checks if is supports full metadata at search.
 * 
 * @return true, if is supports full metadata at search
 */
public boolean isSupportsFullMetadataAtSearch() {
  return supportsFullMetadataAtSearch;
}



/**
 * Checks if is harvestable.
 * 
 * @return true, if is harvestable
 */
public boolean isHarvestable() {
  return harvestable;
}

/**
 * Sets the harvestable.
 * 
 * @param harvestable the new harvestable
 */
public void setHarvestable(boolean harvestable) {
  this.harvestable = harvestable;
}

/**
 * Sets the supports full metadata at search.
 * 
 * @param supportsFullMetadataAtSearch the new supports full metadata at search
 */
public void setSupportsFullMetadataAtSearch(boolean supportsFullMetadataAtSearch) {
  this.supportsFullMetadataAtSearch = supportsFullMetadataAtSearch;
}

/**
 * Checks if is supports spatial query.
 * 
 * @return true, if is supports spatial query
 */
public boolean isSupportsSpatialQuery() {
  return supportsSpatialQuery;
}

/**
 * Sets the supports spatial query.
 * 
 * @param supportsSpatialQuery
 *          the new supports spatial query
 */
public void setSupportsSpatialQuery(boolean supportsSpatialQuery) {
  this.supportsSpatialQuery = supportsSpatialQuery;
}

/**
 * Checks if is supports content type query.
 * 
 * @return true, if is supports content type query
 */
public boolean isSupportsContentTypeQuery() {
  return supportsContentTypeQuery;
}

/**
 * Sets the supports content type query.
 * 
 * @param supportsContentTypeQuery
 *          the new supports content type query
 */
public void setSupportsContentTypeQuery(boolean supportsContentTypeQuery) {
  this.supportsContentTypeQuery = supportsContentTypeQuery;
}

/**
 * Checks if is supports spatial boundary.
 * 
 * @return true, if is supports spatial boundary
 */
public boolean isSupportsSpatialBoundary() {
  return supportsSpatialBoundary;
}

/**
 * Sets the supports spatial boundary.
 * 
 * @param supportsSpatialBoundary
 *          the new supports spatial boundary
 */
public void setSupportsSpatialBoundary(boolean supportsSpatialBoundary) {
  this.supportsSpatialBoundary = supportsSpatialBoundary;
}

// methods =====================================================================

/**
 * Read get metadata by id response.
 * 
 * @param response the response
 * @param record the record
 * @throws TransformerException the transformer exception
 */
public abstract void readGetMetadataByIDResponse(String response, RD record) 
  throws TransformerException ;

/**
 * Read get records response.  Puts SearchXslRecord into search
 * Result param.
 * 
 * @param responseString the response string
 * @param searchResult the search results (will be filled with SearchXslRecords)
 * @throws TransformerException the transformer exception
 * @throws ParserConfigurationException the parser configuration exception
 * @throws SAXException the sAX exception
 * @throws IOException Signals that an I/O exception has occurred.
 * @throws XPathExpressionException the x path expression exception
 */
@SuppressWarnings("unchecked")
public void readGetRecordsResponse(String responseString,
    QueryResult<RS> searchResult) throws TransformerException,
    ParserConfigurationException, SAXException, IOException,
    XPathExpressionException {
  

  LOG.finer("Transforming response to searchxslrecord native " +
  		"response. response = " + responseString);
  String response = this.getResponsexsltobj().transform(responseString);
  RS recordList = searchResult.getRecords();
  
  LOG.finer("CSW Response to CSWClient Native = " + response);
  String RECORD_TAG = "Record";
  // create xml document object
  // XML parser load doc specified by filename
  DocumentBuilder builder = DocumentBuilderFactory.newInstance()
    .newDocumentBuilder();
  Document doc = builder.parse(new InputSource(new StringReader(response)));
  checkForExceptions(doc);
  
  // Get a list of nodes of which root is Profile tag
  NodeList recordNodes = doc.getElementsByTagName(RECORD_TAG);
  // create CswRecord and convert xml document node to each record

  // Get the maximum number of records
  XPath xpath = XPathFactory.newInstance().newXPath();
  @SuppressWarnings("unused")
  NodeList maxRecordNodes = doc.getElementsByTagName("SearchResults");
 
  Node searchResultNode = doc.getDocumentElement();
  NamedNodeMap attributes = searchResultNode.getAttributes();
  Node node = attributes.getNamedItem("maxRecords");
  if(node != null ) {
    int maxNum = Utils.chkInt(node.getNodeValue(), Integer.MIN_VALUE);
    if(maxNum >= 0) {
      recordList.setMaximumQueryHits(maxNum);
      searchResult.setMaxQueryHits(maxNum);
     }
  }
  int nLen = recordNodes.getLength();
  for (int i = 0; i < nLen; i++) {

    LOG.finer("Going through a record node");
    Node currNode = recordNodes.item(i);
    CswRecord record = new CswRecord();

    double maxX = 180;
    double maxY = 90;
    double minX = -180;
    double minY = -90;
    String lowerCorner = "";
    String upperCorner = "";
    record.setDefaultEnvelope(true);

    NodeList nlChildren = currNode.getChildNodes();
    int nChildren = nlChildren.getLength();
    for (int nChild = 0; nChild < nChildren; nChild++) {
      Node ndChild = nlChildren.item(nChild);
      String nodeName = ndChild.getNodeName();
      String nodeValue = ndChild.getTextContent();
      
      if (nodeName.equals("ID")) {
        record.setId(nodeValue);
      } else if (nodeName.equals("Title")) {
        record.setTitle(nodeValue);
      } else if (nodeName.equals("Abstract")) {
        record.setAbstractData(nodeValue);
      } else if (nodeName.equals("ModifiedDate")) {
        record.setModifiedDate(nodeValue);
      } else if (nodeName.equals("MaxX")) {
        maxX = Utils.chkDbl(nodeValue,maxX);
        record.setDefaultEnvelope(
            Utils.chkDbl(nodeValue,Integer.MIN_VALUE) == Integer.MIN_VALUE); 
      } else if (nodeName.equals("MaxY")) {
        maxY = Utils.chkDbl(nodeValue,maxY);
      } else if (nodeName.equals("MinX")) {
        minX = Utils.chkDbl(nodeValue,minX);
      } else if (nodeName.equals("MinY")) {
        minY = Utils.chkDbl(nodeValue,minY);
      } else if (nodeName.equals("LowerCorner")) {
        lowerCorner = Utils.chkStr(nodeValue);
      } else if (nodeName.equals("UpperCorner")) {
        upperCorner = Utils.chkStr(nodeValue);        
      } else if (nodeName.equals("References")) {
        record.setReference(nodeValue);
      } else if (nodeName.equals("Types")) {
        record.setTypes(nodeValue);
      } else if (nodeName.equals("Acl")) {
        record.setAcl(nodeValue);
      }
    }
    
    if (!"".equals(lowerCorner)) {
      String lowerCornerPts[] = lowerCorner.split(" ");
      String upperCornerPts[] = upperCorner.split(" ");
      if (lowerCornerPts != null && lowerCornerPts.length >= 2) {
        minY = Utils.chkDbl(lowerCornerPts[1], minY);
        minX = Utils.chkDbl(lowerCornerPts[0], minX);
        record.setDefaultEnvelope(
            Utils.chkDbl(lowerCornerPts[1], Integer.MIN_VALUE) 
              == Integer.MIN_VALUE); 
      }
      if (upperCornerPts != null && upperCornerPts.length >= 2) {
        maxY = Utils.chkDbl(upperCornerPts[1], maxY);
        maxX = Utils.chkDbl(upperCornerPts[0], maxX);
      }
    }
    record.setEnvelope(new Envelope(minX, minY, maxX, maxY));
             
    // Attempting to see if this is a livedata record
    record.setLiveDataOrMap(false);
    Iterator<DcList.Value> iter = record.getTypes().iterator();
    while (iter.hasNext()) {
      DcList.Value value = iter.next();
      if (value.getValue().equalsIgnoreCase("livedata")) {
        record.setLiveDataOrMap(true);
      } 
    
    }
       
    // Links
    NodeList nodeList = ((Element)currNode).getElementsByTagName("Link");
    for(int j = 0; j < nodeList.getLength(); j++) {
      Node linkNode = nodeList.item(j);
      NamedNodeMap attrLinkNode = linkNode.getAttributes();
      String gptLinkTag = null;
      if(attrLinkNode.getNamedItem("gptLinkTag")!= null) {
        gptLinkTag = attrLinkNode.getNamedItem("gptLinkTag").getNodeValue();
      }
      String show = null;
      if(attrLinkNode.getNamedItem("show")!= null) {
        show = attrLinkNode.getNamedItem("show").getNodeValue();
      }
      String url = null;
      if(linkNode!= null) {
        url = linkNode.getTextContent();
      }
      String label = null;
      if(attrLinkNode.getNamedItem("label")!= null) {
        label = attrLinkNode.getNamedItem("label").getNodeValue();
      }
      
      if(label != null && !"".equals(label)) {
        record.getLinks().addCustomLink(label, url);
      } else if(gptLinkTag != null && !"".equals(gptLinkTag)){
        record.getLinks().addDefaultLinkOptions(gptLinkTag, 
            Val.chkBool(show, true));
      } else {
        continue;
      }
      
    }
    recordList.add((RD)record);
  }
    
  
  
}


/**
 * Support content type query.
 * 
 * @return true, if successful
 */
public boolean SupportContentTypeQuery() {
  return isSupportsContentTypeQuery();
}

public boolean SupportSpatialBoundary() {
  return isSupportsSpatialBoundary();
}

/**
 * Support spatial query.
 * 
 * @return true, if successful
 */
public boolean SupportSpatialQuery() {
  return isSupportsSpatialQuery();
}

public String getDescription() {
  return description;
}

/**
 * Sets the description.
 * 
 * @param description
 *          the new description
 */
public void setDescription(String description) {
  this.description = description;
}

/**
 * Checks if is filter_extentsearch.
 * 
 * @return true, if is filter_extentsearch
 */
public boolean isFilter_extentsearch() {
  return filter_extentsearch;
}

/**
 * Sets the filter_extentsearch.
 * 
 * @param filter_extentsearch
 *          the new filter_extentsearch
 */
public void setFilter_extentsearch(boolean filter_extentsearch) {
  this.filter_extentsearch = filter_extentsearch;
}

/**
 * Checks if is filter_livedatamap.
 * 
 * @return true, if is filter_livedatamap
 */
public boolean isFilter_livedatamap() {
  return filter_livedatamap;
}

/**
 * Sets the filter_livedatamap.
 * 
 * @param filter_livedatamap
 *          the new filter_livedatamap
 */
public void setFilter_livedatamap(boolean filter_livedatamap) {
  this.filter_livedatamap = filter_livedatamap;
}

/**
 * Gets the id.
 * 
 * @return the id
 */
public String getId() {
  return id;
}

/**
 * Sets the id.
 * 
 * @param id
 *          the new id
 */
public void setId(String id) {
  this.id = id;
}

/**
 * Gets the kvp.
 * 
 * @return the kvp
 */
public String getKvp() {
  return kvp;
}

/**
 * Sets the kvp.
 * 
 * @param kvp
 *          the new kvp
 */
public void setKvp(String kvp) {
  this.kvp = kvp;
}

/**
 * Gets the metadataxslt.
 * 
 * @return the metadataxslt
 */
public String getMetadataxslt() {
  return metadataxslt;
}

/**
 * Sets the metadataxslt.
 * 
 * @param metadataxslt
 *          the new metadataxslt
 */
public void setMetadataxslt(String metadataxslt) {
  this.metadataxslt = metadataxslt;
}

/**
 * Gets the metadataxsltobj.
 * 
 * @return the metadataxsltobj
 */
public XsltTemplate getMetadataxsltobj() {
  return metadataXsltObj;
}

/**
 * Sets the metadataxsltobj.
 * 
 * @param metadataxsltobj
 *          the new metadataxsltobj
 */
public void setMetadataxsltobj(XsltTemplate metadataxsltobj) {
  this.metadataXsltObj = metadataxsltobj;
}

/**
 * Gets the name.
 * 
 * @return the name
 */
public String getName() {
  return name;
}

/**
 * Sets the name.
 * 
 * @param name
 *          the new name
 */
public void setName(String name) {
  this.name = name;
}

/**
 * Gets the requestxslt.
 * 
 * @return the requestxslt
 */
public String getRequestxslt() {
  return requestxslt;
}

/**
 * Sets the requestxslt.
 * 
 * @param requestxslt
 *          the new requestxslt
 */
public void setRequestxslt(String requestxslt) {
  this.requestxslt = requestxslt;
}

/**
 * Gets the requestxsltobj.
 * 
 * @return the requestxsltobj
 * @throws TransformerConfigurationException on xslt template creation error
 * @throws IOException Signals that an I/O exception has occurred.
 */
public XsltTemplate getRequestxsltobj() 
  throws TransformerConfigurationException, IOException {
  
  if (requestXsltObj == null) {
    String file = this.getRequestxslt();
    try {
      this.setRequestxsltobj(XsltTemplate.makeFromResourcePath(file));
    } catch (IOException e) {
      try {
        this.setRequestxsltobj(XsltTemplate.makeFromResourcePath(file));
      } catch (IOException f) {
        throw f;
      }
    }
  }
  
  return this.requestXsltObj;
}

/**
 * Sets the requestxsltobj.
 * 
 * @param requestxsltobj
 *          the new requestxsltobj
 */
public void setRequestxsltobj(XsltTemplate requestxsltobj) {
  this.requestXsltObj = requestxsltobj;
}

/**
 * Gets the responsexslt.
 * 
 * @return the responsexslt
 */
public String getResponsexslt() {
  return responsexslt;
}

/**
 * Sets the response XSLT.
 * 
 * @param responsexslt
 *          the new response XSLT
 */
public void setResponsexslt(String responsexslt) {
  this.responsexslt = responsexslt;
}

/**
 * Gets the response XSLT object.
 * 
 * @return the response XSLT object
 * @throws CSWException
 */
public XsltTemplate getResponsexsltobj() throws 
  TransformerConfigurationException, IOException {
  if (responseXsltObj == null) {
    String file = this.getResponsexslt();
    try {
      this.setResponsexsltobj(XsltTemplate.makeFromResourcePath(file));
    } catch (IOException e) {
      try {
        this.setResponsexsltobj(XsltTemplate.makeFromResourcePath(file));
      } catch (IOException f) {
        throw f;
      }
    }
  }
  return responseXsltObj;
}

/**
 * Gets the response XSLT object.
 * 
 * @return the response XSLT object
 * @throws CSWException
 */
public XsltTemplate getMetadataXsltObj() throws TransformerConfigurationException {
  if (this.metadataXsltObj == null) {
    String file = this.getMetadataxslt();
    //InputStream inStream = this.getClass().getResourceAsStream(file);
    //if (inStream == null) {
    //  inStream = this.getClass().getResourceAsStream("/" + file);
    //  file = "/" + file;
    //}
    //inStream = null;
    try {
      this.setMetadataxsltobj(XsltTemplate.makeFromResourcePath(file));
    } catch (TransformerConfigurationException e) {
      LOG.severe("CSW Client: Could not get xslt template " + file);
      throw e;
    } catch (IOException e) {
      LOG.severe("CSW Client: Could not get xslt template " + file);
      throw new TransformerConfigurationException("CSW Client: Could not get xslt template " + file,e);
    }
  }
  return this.metadataXsltObj;
}

/**
 * Sets the response XSLT Object.
 * 
 * @param responsexsltobj
 *          the new response XSLT Object
 */
public void setResponsexsltobj(XsltTemplate responsexsltobj) {
  this.responseXsltObj = responsexsltobj;
}

/**
 * Gets the transformer factory.
 * 
 * @return the factory
 */
public TransformerFactory getFactory() {
  if (factory == null) {
    return TransformerFactory.newInstance();
  }
  return factory;
}

/**
 * Transformer convinient method.
 * 
 * @param xml the xml
 * @param transformer the transformer
 * @return the string
 * @throws TransformerException the transformer exception
 */
protected String transform(String xml, Transformer transformer) 
 throws TransformerException {
  StreamSource source = new StreamSource(new StringReader(xml));
  StringWriter writer = new StringWriter();
  StreamResult result = new StreamResult(writer);
  
  transformer.setOutputProperty(OutputKeys.ENCODING,"UTF-8");
  transformer.setOutputProperty(OutputKeys.INDENT,"yes");
  transformer.transform(source,result);
  
  return result.getWriter().toString();
}

/**
 * Check response if there is any exception produced from the server.
 * 
 * @param doc the doc
 * @exception CswRuntimeException if exception found in response
 */
private void checkForExceptions(Document doc) {
  String exceptionTag = "exception";
  StringBuffer exception = null;
  NodeList recordNodes = doc.getElementsByTagName(exceptionTag);
  
  if(recordNodes == null || recordNodes.getLength() < 1){
    return;
  }
  exception = new StringBuffer();
  XPath xpath = XPathFactory.newInstance().newXPath();
  for (int i = 0; i < recordNodes.getLength(); i++) {
    
   
    try {
      exception.append(
          Utils.chkStr(xpath.evaluate("exceptionText", recordNodes.item(i))));
    } catch (XPathExpressionException e) {
      exception.append(e.getMessage());
      LOG.log(Level.WARNING, "Problem while parsing execption recieved", e);
      e.printStackTrace();
    }
  
  }
  if(exception != null) {
    LOG.severe("Search returned exception. Creating exception report "
        + exception.toString());
    throw new CswRuntimeException(exception.toString());
  }
 
}

/**
 * Read intermidiate get record by id.  GetRecords does only uses the getMetadata
 * xslt to get an intermidiate reference to the actual xml
 * 
 * @param response the response
 * @param record the record
 * @throws TransformerConfigurationException the transformer configuration exception
 * @throws TransformerException the transformer exception
 */
protected void readIntermidiateGetRecordById(String response, 
    SearchXslRecord record) 
  throws TransformerConfigurationException, TransformerException 
{
  String metadataxslt = this.getMetadataxslt();
  if (metadataxslt == null || metadataxslt.equals("")) {
    record.setFullMetadata(Utils.chkStr(response));
  } else {
 
    LOG.finer("Transforming GetRecordByID intermidiate xml to GetRecordById " +
        "Native");
    
    String result = this.getMetadataXsltObj().transform(Val.chkStr(response));
        
    String xmlUrl = null;
    String dctReferences = result;
    LOG.finer("Native GetRecordBYID from transform = " + dctReferences);
    DcList lstDctReferences = new DcList(dctReferences);
    
    Iterator<DcList.Value> iter = lstDctReferences.iterator();
    while(iter.hasNext()) {
      DcList.Value value = iter.next();
      if(value.getValue().toLowerCase().endsWith(".xml") 
          || value.getScheme().equals(SCHEME_METADATA_DOCUMENT)) {
        xmlUrl = value.getValue();
      }
    }
    record.setReferences(lstDctReferences);
    LOG.finer("URL to view full metadata document found = " + xmlUrl);
    record.setMetadataResourceURL(xmlUrl);
  }
}

/**
 * Gets the record by id.
 * 
 * <ul>
 *   <li>Url constructed</li>
 *   <li>XML is gotten from url</li>
 *   <li>XML put through the getRecordsResponse xsl to make a cswRecord</li> 
 *   <li>if metadataxsl exists, use it to get the use responstr an intermidiate
 * response and get the associated reference</li>
 *   <li>Fills the cswrecord with the responseStr and later with the intermidiate
 * xml reference response if there is one
 *   </li>
 * <ul>
 *   
 * 
 * 
 * @param responseStr the respons str
 * @param uuid the uuid
 * @param requestUrl the request url
 * @return the record by id
 * @throws IOException Signals that an I/O exception has occurred.
 * @throws TransformerException the transformer exception
 * @throws NullReferenceException the null reference exception
 * @throws InvalidOperationException the invalid operation exception
 */
@SuppressWarnings("unchecked")
public SearchXslRecord getRecordById(String responseStr, String uuid, 
    String requestUrl) 
  throws IOException, TransformerException,
    NullReferenceException, InvalidOperationException, SearchException {
  SearchXslRecord record = null;

        
    responseStr = Val.chkStr(responseStr);    
    
    try {
      LOG.finer("GetRecordById: Making csw record object using g" +
          "etRecordsResponse operation");
    
      // making cswObject by going through getrecord response xslt
      Records<SearchXslRecord> recordList = new Records<SearchXslRecord>();
      QueryResult<RS> results = new QueryResult<RS>();
      this.readGetRecordsResponse(responseStr, results);
   
      Iterator<SearchXslRecord> iter = recordList.iterator();
      if(iter.hasNext()) {
         record = iter.next(); 
        
      } else  {
        LOG.log(Level.WARNING, 
            "Could not get csw metadata of metadata document");
      }
    } catch (ParserConfigurationException e) {
      LOG.log(Level.INFO, 
        "Could not get csw metadata of metadata document (maybe this csw does " 
        + "not have csw metadata on getRecord by id. " + e.getMessage());
    } catch (SAXException e) {
      LOG.log(Level.INFO, 
        "Could not get csw metadata of metadata document (maybe this csw does " 
        + "not have csw metadata on getRecord by id. " + e.getMessage());
    } catch (XPathExpressionException e) {
      LOG.log(Level.INFO, 
        "Could not get csw metadata of metadata document (maybe this csw does " 
        + "not have csw metadata on getRecord by id. " + e.getMessage());
    }
    record.setId(uuid);
    LOG.finer("GetRecordByID: Transforming intermidiate xml to populate xml " +
        "into csw Record");
    this.readIntermidiateGetRecordById(responseStr, record);
    if (record == null) {
      throw new NullReferenceException("Record not populated.");
    }
    // check if full metadata or resourceURL has been returned
    boolean hasFullMetadata = !(record.getFullMetadata() == null || record
        .getFullMetadata() == "");
    boolean hasResourceUrl = !(record.getMetadataResourceURL() == null || record
        .getMetadataResourceURL() == ""); // TODO: CHECK THE COMPARISONS!
    
    if(!hasResourceUrl && requestUrl != null ) {
      record.setMetadataResourceURL(requestUrl);
      hasResourceUrl = true;
    }

    if (!hasFullMetadata && !hasResourceUrl) {
      throw new SearchException("Neither full metadata nor metadata"
          + " resource URL was found for the CSW record.");
    } else if (hasResourceUrl) {
      // need to load metadata from resource URL
      URL url = null;
      Exception ex = null;
      try {
        url = new URL(record.getMetadataResourceURL());

        HttpClientRequest clientRequest = HttpClientRequest.newRequest(
            HttpClientRequest.MethodName.GET, url.toExternalForm());
        // clientRequest.setConnectionTimeOut(getConnectionTimeout());
        //clientRequest.setResponseTimeOut(getResponseTimeout());
        clientRequest.execute();
        String response = clientRequest.readResponseAsCharacters();
        LOG.finer("Response from get Metadata url = " + url.toExternalForm()
            +"\n response = \n"+ response);
        record.setFullMetadata(response);
      } catch (MalformedURLException e) {
        ex = e;
      } catch (IOException e) {
        ex = e;
      }
      if(ex != null) {
        throw new SearchException("Could not get metadata id url = " + url, ex);
      }

      
    }

 
  return record;

}

/**
 * Gets the format request to xsl.
 * 
 * @return the format request to xsl (never null, default MINIMAL_LEGACY)
 */
public FORMAT_SEARCH_TO_XSL getFormatRequestToXsl() {
  if(this.formatRequestToXsl == null) {
    this.formatRequestToXsl = FORMAT_SEARCH_TO_XSL.MINIMAL_LEGACY_CSWCLIENT;
  }
  return formatRequestToXsl;
}

/**
 * Sets the format request to xsl.
 * 
 * @param formatRequestToXsl the new format request to xsl
 */
public void setFormatRequestToXsl(FORMAT_SEARCH_TO_XSL formatRequestToXsl) {
  this.formatRequestToXsl = formatRequestToXsl;
}


/**
 * Generate get records request.
 * 
 * @param criteria the criteria
 * @param xslParams the xsl params
 * @param hitsOnly Indicates whether this is a hits only request
 * @return the string
 * @throws TransformerException the transformer exception
 * @throws ParserConfigurationException the parser configuration exception
 * @throws SAXException the sAX exception
 * @throws IOException Signals that an I/O exception has occurred.
 * @throws SearchException the search exception
 * @throws XPathExpressionException the x path expression exception
 */
public String generateGetRecordsRequest(SearchCriteria criteria,
    Map<String, String> xslParams, boolean hitsOnly)
    throws TransformerException, ParserConfigurationException, SAXException,
    IOException, SearchException, XPathExpressionException {

  if(xslParams == null) {
    xslParams = new HashMap<String, String>();
  }
  xslParams.put(XSL_PARAM_HITS_ONLY, String.valueOf(hitsOnly));
  String internalRequestXml = null;
  FORMAT_SEARCH_TO_XSL formatRequestToXsl = this.getFormatRequestToXsl();
  if (formatRequestToXsl == FORMAT_SEARCH_TO_XSL.FULL_NATIVE_GPTXML) {
    internalRequestXml = criteria.toDom2();

  } else if (formatRequestToXsl == FORMAT_SEARCH_TO_XSL.DETAILED_GPT_CSW202) {
    internalRequestXml = SearchEngineCSW.transformGptToCswRequest(criteria,
        SearchEngineCSW.SEARCH_OPERATION.doSearch);
  } else {
    // Default use the MINIMAL XML
    CswSearchCriteria cswSearchCriteria = SearchEngineCSW
        .marshallGptToCswClientCriteria2(criteria);
    internalRequestXml = cswSearchCriteria.toXml();
  }

  LOG.finer("Generated internal XML requst input  to request xsl "
      + internalRequestXml);
  internalRequestXml = getRequestxsltobj().transform(internalRequestXml,
      xslParams);
  LOG.finer("Transform output from Request xsl " + internalRequestXml);

  return internalRequestXml;
}

}
