package com.k_int.discover.sru.action;

import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URLDecoder;
import java.security.Principal;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.MissingResourceException;
import java.util.ResourceBundle;
import java.util.Map.Entry;

import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.solr.client.solrj.SolrServer;
import org.apache.solr.client.solrj.response.FacetField;
import org.apache.solr.client.solrj.response.QueryResponse;
import org.apache.solr.client.solrj.response.FacetField.Count;
import org.apache.solr.common.SolrDocument;
import org.apache.solr.common.SolrDocumentList;
import org.apache.solr.common.params.ModifiableSolrParams;
import org.apache.struts2.interceptor.ServletRequestAware;

import javax.servlet.http.HttpServletRequest;


import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.z3950.zing.cql.CQLAndNode;
import org.z3950.zing.cql.CQLBooleanNode;
import org.z3950.zing.cql.CQLNode;
import org.z3950.zing.cql.CQLNotNode;
import org.z3950.zing.cql.CQLOrNode;
import org.z3950.zing.cql.CQLParseException;
import org.z3950.zing.cql.CQLParser;
import org.z3950.zing.cql.CQLTermNode;

import com.k_int.srusolr.util.ExternalResourceResolver;
import com.k_int.srusolr.util.InvalidSRUIndexException;
import com.k_int.srusolr.util.SolrIndexConverter;
import com.k_int.svc.identity.service.IdentityService;
import com.k_int.svc.identity.service.SystemUserDTO;

import com.opensymphony.xwork2.ActionSupport;

public class SRUBaseAction extends ActionSupport implements ServletRequestAware, ApplicationContextAware {
  public static Log log = LogFactory.getLog(SRUBaseAction.class);
  private static final long serialVersionUID = 1L;
  private static final String SIM_KEY = "strict_index_mapping";
  private HttpServletRequest request;
  private ApplicationContext ctx;
  private Map<String, String> cql_index_to_solr_term;
 // private Map<String, Class<RecordBuilder>> schema_to_builder;
  private Map<String, SolrIndexConverter> solr_index_converters;
  private Boolean strict_index_mapping;
  private String stylesheet;
  private String version = "1.1";
  private String host;
  private String port;
  private String database;
  private Map<String, List<String>> valid_indexes;
  private String record_count_str;
  private List<String> result_docs;
  private Integer last_record;
  private long record_count;
  private Integer first_record;
  private String query;
  private String record_schema;
  private String diagnostic_uri;
  private String diagnostic_details;
  private String diagnostic_message;
  private String resource_bundle_location;
  private String resource_bundle_base_name;
  private String add_info;
  private Map<String, String> extra_response_props  = new HashMap<String,String>();
  private boolean secured                           = false;
  private IdentityService identity_service          = null;
  private List<String> array_element_exceptions     = null;
  private SolrServer solr_server                    = null;
  private List<FacetField> facets = null;
  
  public void setServletRequest(HttpServletRequest request) { this.request = request; }
  public void setApplicationContext(ApplicationContext ctx) { this.ctx = ctx;         }
  public void setResourceBundleLocation(String resource_bundle_location) { this.resource_bundle_location = resource_bundle_location; }
  public void setResourceBundleBaseName(String resource_bundle_base_name) { this.resource_bundle_base_name = resource_bundle_base_name; }
  public void setSolrServer(SolrServer solr_server) { this.solr_server = solr_server; }
  public void setFacets(List<FacetField> facets) { this.facets = facets; }
  
  public void setArrayElementExceptions(List<String> array_element_exceptions) { this.array_element_exceptions = array_element_exceptions; }
  public List<String> getArrayElementExceptions() { return array_element_exceptions; }
  
 
  
  // shared
  public String getStylesheet()  { return stylesheet; }
  public String getVersion()     { return version;    }

  // explain
  public String getHost()       { return host;                 }
  public String getPort()       { return port;                 }
  public String getDatabase()   { return database;             }
  public Integer getIndexSize() { return valid_indexes.size(); }
  public Map<String, List<String>> getValidIndexes() { return valid_indexes; }
 
  // searchRetrieve
  public String getRecordCount()      { return record_count_str; }
  public List<String> getResultDocs() { return result_docs; }
  public Integer getResultDocsSize()  { return result_docs.size(); }
  public Integer getLastRecord()      { return last_record; }
  public boolean getMoreRecords()     { return (last_record<record_count && last_record>0); }
  public Integer getNextRecord()      { return last_record+1; }
  public Integer getFirstRecord()     { return first_record; }
  public String getSafeQuery()        { return query.replaceAll("\\&","&amp;").replaceAll("<","&lt;"); }
  public String getRecordSchema()     { return record_schema; }
  public String getAddInfo()          { return add_info; }
  public Map<String,String> getExtraRespProps()   { return extra_response_props; }
  public SolrServer getSolrServer()   { return solr_server; }
  public List<FacetField> getFacets()   { return facets; }
  
  // diagnostics
  public String getDiagnosticUri()     { return diagnostic_uri;     }
  public String getDiagnosticDetails() { return diagnostic_details; }
  public String getDiagnosticMessage() { return diagnostic_message; }

  public void setIdentityService(IdentityService identity_service) {
    this.identity_service = identity_service;
  }

  public IdentityService getIdentityService() {
    return identity_service;
  }

  public boolean getSecured() {
    return secured;
  }

  public void setSecured(boolean secured) {
    this.secured = secured;
  }
  
  public SRUBaseAction() {}

  public void init() throws ClassCastException, ClassNotFoundException, IllegalAccessException, InstantiationException, MalformedURLException {
    log.debug("*******************");
    log.debug("SRUBaseAction::SRUBaseAction");
    log.debug("*******************");

    ResourceBundle index_mapping = null;

    try 
    {
      index_mapping = ExternalResourceResolver.resolve(resource_bundle_location, resource_bundle_base_name, "com.k_int.aggregator.home");
      
    } 
    catch (MissingResourceException mre) 
    {
      // Don't deal with this here. If the resource bundle cannot be found feedback at request-time.
      // Stack trace for information only, no functional impact of exception at this point
      mre.printStackTrace();
    }
    
    if (index_mapping!=null) 
    {
      cql_index_to_solr_term            = new HashMap<String, String>();
      Enumeration<String> mapping_keys  = index_mapping.getKeys();
    
      while (mapping_keys.hasMoreElements()) 
      {
        String mapping_key   = mapping_keys.nextElement();
        if (!mapping_key.equals(SIM_KEY)) 
        {
          String index_mapping_value = index_mapping.getString(mapping_key);
          if (index_mapping_value==null || index_mapping_value.trim().equals((""))) index_mapping_value=null;
          cql_index_to_solr_term.put(mapping_key, index_mapping_value);
        }
      }
      
      String sim = null;
      try 
      {
        sim = index_mapping.getString(SIM_KEY);
      } 
      catch (MissingResourceException mre) 
      {
        // ignore and default to true
      }
      strict_index_mapping = (sim!=null && sim.equals("false")) ? false : true;
      
    } 
    else 
    {
      strict_index_mapping   = true;
      cql_index_to_solr_term = null;
    }
    
    
    ResourceBundle solr_converter_mapping = null;
    try 
    {
      solr_converter_mapping = ResourceBundle.getBundle("solr_converter_mapping");
    } 
    catch (MissingResourceException mre) 
    {
      // Don't deal with this here. If the resource bundle cannot be found feedback at request-time.
    }
    
    if (solr_converter_mapping!=null) 
    {
      solr_index_converters             = new HashMap<String, SolrIndexConverter>();
      Enumeration<String> mapping_keys  = solr_converter_mapping.getKeys();
    
      while (mapping_keys.hasMoreElements()) 
      {
        String mapping_key   = mapping_keys.nextElement();
        String mapping_value = solr_converter_mapping.getString(mapping_key);
        
        if (mapping_value!=null) 
        {
          solr_index_converters.put(mapping_key, (SolrIndexConverter) ctx.getBean(mapping_value));
        }
      }
    } 
    else 
    {
      solr_index_converters = null;
    }

    // no implementations created yet.
    //schema_to_builder    = new HashMap<String, Class<RecordBuilder>>();
  }
  

  public String execute() {

    if ( secured ) {
      log.debug("SRU Interface is secured - checking for presence of API-KEY");
      String api_key = request.getParameter("apikey");
      if ( api_key != null ) {
        try {
          SystemUserDTO user = identity_service.authenticate(api_key);
          if ( user != null ) {
            log.debug("user authenticated");
          }
          else {
            diagnostic_uri     = "1/3";
            diagnostic_details = "0";
            diagnostic_message = "Unable to validate API Key";
            return ERROR;
          }
        }
        catch ( com.k_int.svc.identity.service.IdentityServiceException ise ) {
          diagnostic_uri     = "1/3";
          diagnostic_details = "0";
          diagnostic_message = "Unable to validate API Key";
          return ERROR;
        }
      }
      else {
        diagnostic_uri     = "1/3";
        diagnostic_details = "0";
        diagnostic_message = "SRU Server is configured in secure mode but no apikey property could be found in the request";
        return ERROR;
      }
    }

    // Thats security out of the way, continue
    
    String result = null;    

    String operation = request.getParameter("operation");
    log.debug("operation="+operation);
              

    // If the request contains a stylesheet definition
    stylesheet = request.getParameter("stylesheet");
    if (stylesheet!=null && stylesheet.length()<1) stylesheet=null;

    // Operation specific switch here
    if (operation!=null) {
        if ( operation.toLowerCase().equals("scan") ) {
            diagnostic_uri     = "1/4";
            diagnostic_details = "0";
            diagnostic_message = "The scan operation is not currently supported";
            return ERROR;
        }
        else if (operation.toLowerCase().startsWith("info:srw/operation/1/")) {
            if (operation.toLowerCase().equals("info:srw/operation/1/update")) {
                result = processRecordUpdate();
            }
        } else if (operation.toLowerCase().equalsIgnoreCase("searchRetrieve")) {
            result = processSearchRetrieve();
        }
    } 
    
    if (result==null) {
      result = processExplain();
    }
    
    return result;
  }

  
  /**
   * Return an SRW Explain response.
   * @return void
   */
  private String processExplain() {
    log.debug("processExplain");
    String result = "explain";
    
    host     = request.getLocalName();
    port     = ""+request.getLocalPort();
    database = request.getContextPath()+request.getServletPath();
    
    valid_indexes = new HashMap<String, List<String>>();
    
    for (Entry<String, String> index : cql_index_to_solr_term.entrySet()) {
      log.debug("testing "+index);
      if (index.getValue()!=null && !index.getValue().trim().equals("")) {
        String set = null;
        String idx = null;
        if (index.getValue().indexOf(".")>-1) {
          set = index.getValue().substring(0, index.getValue().indexOf("."));
          idx = index.getValue().substring(index.getValue().indexOf(".")+1); 
          if (set!=null && idx!=null) {
            if (valid_indexes.get(set)==null) valid_indexes.put(set, new ArrayList<String>());
            valid_indexes.get(set).add(idx);
          }
          else {
            log.warn("Set or idx was null");
          }
        }
      }
      else {
        log.warn("Index name did not contain a dot - Don't know why thats assumed to be an error");
      }
    }
    
    return result;
  }

  
  /**
   * handler for record upload.
   * Not yet implemented fully
   * @return void
   */
  @SuppressWarnings("unchecked")
  private String processRecordUpdate() 
  {
    String result = "recordUpdate";
    /* Unused vars.
    String identifier = request.getParameter("identifier");
    String record_source = request.getParameter("record_source");
    String publisher_identity = request.getParameter("publisher_identity");
    String publisher_credentials = request.getParameter("publisher_credentials");
    String target_collection = request.getParameter("target_collection");
    */
    try {
      boolean isMultipart = ServletFileUpload.isMultipartContent(request);
      if ( isMultipart ) {
        ServletFileUpload upload = new ServletFileUpload();
        List<FileItem> items = (List<FileItem>)upload.parseRequest(request);

        Iterator<FileItem> iter = items.iterator();
        while (iter.hasNext()) {
          FileItem item = iter.next();
          log.debug("field name:"+item.getFieldName());
          log.debug("file name:"+item.getName());
          log.debug("In mem:"+item.isInMemory());
          if (item.isFormField()) {
            //String record = item.getString();
            // publish(identifier,record_source,publisher_identity,publisher_credentials,target_collection,record);
          } else {
            // processUploadedFile(item);
            //String record = item.getString();
            // publish(identifier,record_source,publisher_identity,publisher_credentials,target_collection,record);
          }
        }
      }
      else {
        log.debug("Not multipart");
      }
    } catch (FileUploadException fue) {
      fue.printStackTrace();
    }
    
    return result;
  }

  
  /**
   * Process search retrieve response.
   * @return void
   */
  @SuppressWarnings("unchecked")
  private String processSearchRetrieve() 
  {
      log.debug("processSearchRetrieve");
      
      String result = "searchRetrieve";
      query         = request.getParameter("query");
      version       = request.getParameter("version");
      
      if(version==null)
          version="1.1";
            
      /**if ( version == null ) 
      {
          diagnostic_uri     = "1/7";
          diagnostic_details = "0";
          diagnostic_message = "Version parameter not present";
          return ERROR;
      }**/
    
      record_schema             = request.getParameter("recordSchema")!=null ? request.getParameter("recordSchema") : "solr"; 
      String start_record       = request.getParameter("startRecord");
      String maximum_records    = request.getParameter("maximumRecords");
      
      if(maximum_records==null)
          maximum_records="10";
      
      extra_response_props.put("maximumRecords",maximum_records);
      String sortBy             = request.getParameter("sortBy");
      String sortKeys           = request.getParameter("sortKeys");
      String sortQuery          = createSortQuery(sortBy);
      first_record              = start_record==null ? 1 : Integer.parseInt(start_record);

      ModifiableSolrParams params = new ModifiableSolrParams();
      
      
      if (sortBy != null) 
      {
          // Handle the SRU 1.2 sorting method
          sortQuery = createSortQuery(sortBy);
      } 
      else if (sortKeys != null ) 
      {
          // Handle the SRU 1.1 sorting method
          sortQuery = createSortQuery(sortKeys);
      }
        
      if ( !"".equals(sortQuery) ) 
      {
          if ( sortBy != null ) 
          {
            params.set("sort", sortQuery);
            extra_response_props.put("sortBy",sortBy);
          } 
          else if ( sortKeys != null ) 
          {
            params.set("sort", sortQuery);
            extra_response_props.put("sortKeys",sortKeys);
          }
      }
        
      
      // If we're a user that has edit permissions, etc. then add an editable flag
      Principal principal = request.getUserPrincipal();
      if ( principal != null) {
    	  log.debug("principal.getName: " + principal.getName());
    	  log.debug("principal.toString: " + principal.toString());
      } else {
    	  log.debug("principal is null...");
      }
      if(request.isUserInRole("SYSTEM.admin") || request.isUserInRole("GLOBAL.admin") || request.isUserInRole("com.k_int.aggregator.roles.Editor")) {
    	  extra_response_props.put("editable", "true");
    	  log.debug("admin of some sort - going to add editable permissions");
      } else {
    	  log.debug("not an admin, so not adding editable permissions");
      }
      
      String[]use_default = request.getParameterValues("x-facet");
    
      
      if(use_default!=null && use_default.length>0 && use_default[0].equalsIgnoreCase("default"))
      {
          String[] facet_values = new String[]{"dcterms.isPartOf","dcmi.type","dc.subject"};
          params.set("facet",true);
          params.set("facet.field",facet_values);       
          params.set("facet.mincount",1);
          params.set("facet.limit",30);  
          
          Enumeration<String> e = request.getParameterNames();
          while(e.hasMoreElements())
          {
              String param_name=e.nextElement();
              if(param_name.startsWith("x-facet"))
              {;}
              else if(param_name.startsWith("x-"))
              {
                  String[] values = request.getParameterValues(param_name);
                  if(values!=null && values.length>0)
                  {
                      if(values.length==1)
                      {
                          params.set(param_name.substring(2),values[0]);
                      }
                      else
                          params.set(param_name.substring(2),values);
                  }                 
              }              
          }          
      }
      else
      {           
          Enumeration<String> e = request.getParameterNames();
       
          while(e.hasMoreElements())
          {
              String param_name = e.nextElement();
              if(param_name.startsWith("x-"))
              {
                  String[] values = request.getParameterValues(param_name);
                  if(values!=null && values.length>0)
                  {
                      if(values.length==1)
                      {
                          params.set(param_name.substring(2),values[0]);
                      }
                      else
                          params.set(param_name.substring(2),values);
                  }
              }     
          }
      }
  
      if ( first_record == 0 ) 
      {
          // This is really error 61 - records First record position out of range For example, if the request matches 10 records, but the start position is greater than 10.
          diagnostic_uri     = "1/61";
          diagnostic_details = "0";
          diagnostic_message = "First record position out of range";
          return ERROR;
      }

      // 1. Parse the query
      try 
      {
          CQLParser parser      = new CQLParser();
          CQLNode cql_query     = parser.parse(query);

          log.debug("Parse complete");

          GeneralQueryModel gqm = new GeneralQueryModel();

          String solr_query = cqlToSolr(cql_query,cql_query, gqm);
          String filter_query = null;

          if ((gqm.getQueryType() != null) && (gqm.getQueryType().equals("geo"))) 
          {
              if (gqm.getRadius() == null) 
              {
                  if ( request.getParameter("radius") != null )
                      gqm.setRadius(request.getParameter("radius"));
                  else
                      gqm.setRadius("5");
              }
              extra_response_props.put("geo_lat",gqm.getLat());
              extra_response_props.put("geo_lon",gqm.getLng());
              extra_response_props.put("geo_rad",gqm.getRadius());
//              params.set("lat",gqm.lat);
//              params.set("long",gqm.lng);
//              params.set("radius",gqm.radius);
              filter_query = "{!spatial lat=" + gqm.getLat() + " long=" + gqm.getLng() + " radius=" + gqm.getRadius() + " unit=miles} ";

              
      }
   
      log.debug("Mapped SRU to Solr:");
      log.debug("cql_query:  "+cql_query);
      log.debug("solr_query:  "+solr_query);
      log.debug("gqm:  "+gqm);

      int solr_start    = first_record==0 ? 0 : first_record-1;

      params.set("q", filter_query != null ? filter_query+solr_query : solr_query);
      params.set("start", solr_start);
      params.set("rows", maximum_records);

      log.debug("params = " + params);
      QueryResponse response    = solr_server.query(params);
     
      SolrDocumentList sdl      = response.getResults();

      // Log this search..
      String sourceIP = (String)request.getHeader("x-forwarded-for");
      if ( sourceIP == null || "".equals(sourceIP.trim()) )
            sourceIP = request.getRemoteAddr();
      String referer = (String)request.getHeader("referer");


      Long numOfResults = response.getResults().getNumFound();
      SRULogging.outputLoggingInformation(sourceIP, referer, query, numOfResults);
      
      log.debug("Solr request url: "+response.getRequestUrl());
      record_count              = sdl.getNumFound();
      
      last_record               = 0;

      result_docs = new ArrayList<String>();
      for (Iterator<SolrDocument> results_iterator = sdl.iterator(); results_iterator.hasNext(); ) 
      {
        SolrDocument doc = (SolrDocument) results_iterator.next();
        result_docs.add(SolrDocToString(doc));
      }
      last_record = first_record+result_docs.size()-1;
   
      facets = response.getFacetFields();
     
      StringBuilder sb = new StringBuilder();
      
     
      if(facets!=null)
      {
          sb.append("<lst name=\"facet_counts\" xmlns=\"http://k-int.com/facet\"><lst name=\"facet_fields\">");
          Iterator<FacetField> i = facets.iterator();
         
          while(i.hasNext())
          {
              FacetField ff = i.next();
              appendSolrFacet(ff,sb,record_count);                    
          }
          
          sb.append("</lst></lst>");
      }
         
      /*if (gqm.disambiguation.containsKey("Location")) 
      {
			log.debug("Location disambiguation found");
			ArrayList<GazetteerPlace> locations = (ArrayList<GazetteerPlace>) gqm.disambiguation.get("Location");
			if (locations.size() > 0) 
			{
				String disambiguation = disambiguation(locations,
						solr_result_document, api, cql_query);
				add_info = add_info == null ? disambiguation : add_info
						.concat(disambiguation);
			}
      }**/
      add_info=sb.toString();
      record_count_str=Long.toString(record_count);
    } 
      catch (CQLParseException cpe) 
      {
      log.error("problem",cpe);
      diagnostic_uri     = "1/1";
      diagnostic_details = "0";
      if ( cpe.getMessage() != null )
        diagnostic_message = cpe.getMessage();
      else 
        diagnostic_message = cpe.toString();
      return ERROR;
      } 
      catch (InvalidSRUIndexException isie) 
      {
          result = processDiagnostic(isie);
      } 
      catch (Exception e) 
      {
          log.error("problem", e);
          diagnostic_uri     = "1/1";
          diagnostic_details = "0";
          if ( e.getMessage() != null )
              diagnostic_message = e.getMessage();
          else 
              diagnostic_message = e.toString();

          return ERROR;
      }
      
      
      // If the stylesheet is not on the local server (identified by 
      // the stylesheet path starting with http) then don't do any
      // styling on the server, rely on this being done client side
//      if ( this.stylesheet != null && this.stylesheet.startsWith("http") ) {
//    	  log.debug("Setting that no XSLT processing be performed on the server..");
//    	  // A remote stylesheet..
//    	  request.setAttribute(XsltFilterConstants.NO_XSLT_PROCESSING, Boolean.TRUE);
//      }
// TODO - can this be replaced?
      
    return result;
  }
  
  
  private String processDiagnostic(InvalidSRUIndexException isie) {
	  diagnostic_uri     = "1/16";
	  diagnostic_details = isie.getIndex();
	  diagnostic_message = isie.getMessage();
	  
	  return ERROR;
  }

  
  private String cqlToSolr(CQLNode root, CQLNode cql_query, GeneralQueryModel gqm) throws UnsupportedEncodingException, InvalidSRUIndexException 
  {
    if (cql_index_to_solr_term==null) 
    {
      throw new NullPointerException("Index to Term mapping was null, resource bundle was probably not found.");
    }
    
//    if (solr_index_converters==null) 
//    {
//      throw new NullPointerException("SOLR converter mapping was null, resource bundle was probably not found.");
//    }
    
    String solr_query = "";
    if (cql_query instanceof CQLBooleanNode) {

      if (cql_query instanceof CQLAndNode) {
        CQLAndNode and = (CQLAndNode) cql_query;
        solr_query += processBool(root, and.left, and.right, "AND", gqm);
      } else if (cql_query instanceof CQLNotNode) {
        CQLNotNode not = (CQLNotNode) cql_query;
        solr_query += processBool(root, not.left, not.right, "NOT", gqm);
      } else if (cql_query instanceof CQLOrNode) {
        CQLOrNode or = (CQLOrNode) cql_query;
        solr_query += processBool(root, or.left, or.right, "OR", gqm);
      } else {
        log.warn("Not processing CQLNode class of "+cql_query.getClass());
      }
    } else if (cql_query instanceof CQLTermNode) {
      solr_query = processTermNode((CQLTermNode) cql_query, gqm);
    } else {
      log.warn("Not processing CQLNode class of "+cql_query.getClass());
    } 
    return solr_query;
  }

  private String processTermNode(CQLTermNode term, GeneralQueryModel gqm) throws UnsupportedEncodingException, InvalidSRUIndexException {
    String solr_query = "";
    String rel       = term.getRelation().getBase();

    if (URLDecoder.decode(rel, "UTF-8").equals("=")) {
      rel = ":";
    }

    String index = term.getIndex();
    
    log.debug("in processTermNode - index = " + index);
     

    // default value for cql.serverChoice
    if (index.equals("cql.serverChoice")) {
        if ("*".equals(term.getTerm()))
        	solr_query = "[* TO *]";
        else
        	solr_query = term.getTerm();
    } else { 
      // check if the term is convertible with a class specified by mapping properties
      if ((solr_index_converters != null) && solr_index_converters.containsKey(index)) {
        try {
          SolrIndexConverter sic = solr_index_converters.get(index);
          Map<String, String> converted_indecies = sic.convert(term.getTerm(), gqm);
          if (converted_indecies.size()>0) {
            Iterator<Entry<String, String>> idx_it = converted_indecies.entrySet().iterator();
            while (idx_it.hasNext()) {
              Entry<String, String> idx_ent = idx_it.next();
              solr_query += buildQueryComponent(idx_ent.getKey(),rel,idx_ent.getValue());
              // solr_query += idx_ent.getKey()+rel+"(\""+idx_ent.getValue()+"\")";
              if (idx_it.hasNext()) solr_query+=" AND ";
            }
          }
        }
        catch ( Exception e ) {
          throw new InvalidSRUIndexException("Problem mapping \""+index+"\" - "+e.getMessage(), index );
        }
        finally {
        }
      } else {     
        // check if the term is allowable by mapping properties
        if (!cql_index_to_solr_term.containsKey(index) && strict_index_mapping) {
          throw new InvalidSRUIndexException("There is no mapping defined from SRU index of \""+index+"\" to a SOLR term.", index);
        } else if (cql_index_to_solr_term.get(index)==null) {
          throw new InvalidSRUIndexException("The mapping from SRU index of \""+index+"\" to a SOLR term is explicitly denied.", index); 
        } else {
          index = cql_index_to_solr_term.get(index); 
        }
        
        solr_query += buildQueryComponent(index,rel,term.getTerm());
      }
    }

    return solr_query;
  }

  public String processBool(CQLNode root, CQLNode lhs, CQLNode rhs, String op, GeneralQueryModel gqm) throws java.io.UnsupportedEncodingException,com.k_int.srusolr.util.InvalidSRUIndexException {
    String lhs_solr = cqlToSolr(root,lhs,gqm);
    String rhs_solr = cqlToSolr(root,rhs,gqm);
    if ( ( lhs_solr != null ) && ( rhs_solr != null ) && ( lhs_solr.length() > 0 ) && ( rhs_solr.length() > 0 ) ) {
      return "("+lhs_solr+") "+op+" ("+rhs_solr+")";
    }
    else if ( ( lhs_solr != null ) && ( lhs_solr.length() > 0 ) ) {
      return lhs_solr;
    }
    else if ( ( rhs_solr != null ) && ( rhs_solr.length() > 0 ) ) {
      return rhs_solr;
    }

    return null;
  }

  public static String buildQueryComponent(String index, String rel, String term) throws InvalidSRUIndexException {
  
	if ( rel != null ) {
      if ( rel.equalsIgnoreCase(">") ) {
        return index+":{"+term+" TO *}";
      }
      if ( rel.equalsIgnoreCase(">=") ) {
        return index+":["+term+" TO *]";
      }
      else if ( rel.equalsIgnoreCase("<") ) {
        return index+":{* TO "+term+"}";
      }
      else if ( rel.equalsIgnoreCase("<=") ) {
        return index+":[* TO "+term+"]";
      }
      else if ( rel.equalsIgnoreCase("adj") ) {
        return index+":(\""+term+"\")";
      }
      else if ( rel.equalsIgnoreCase("all") ) {

		  StringBuilder returnValueBuilder = new StringBuilder();

    	  if ("*".equals(term)) {
            	term = "[* TO *]";
            	returnValueBuilder.append(index);
            	returnValueBuilder.append(":(");
            	returnValueBuilder.append(term);
            	returnValueBuilder.append(")");
    	  } else {
    		  // Split the search term into several requests
	    	  String[] splitTerms = term.split(" ");
	    	  for(String thisTerm: splitTerms) {
	    		  if (thisTerm != null && !"".equals(thisTerm.trim()) ) {
	    			  // We have another term to add to the query
	    			  if ( returnValueBuilder.length() > 0 ) {
	    				  returnValueBuilder.append(" AND ");
	    			  }
	    			  returnValueBuilder.append(index);
	    			  returnValueBuilder.append(":(\"");
	    			  returnValueBuilder.append(thisTerm.trim());
	    			  returnValueBuilder.append("\")");
	    		  }
	    	  }
    	  }
    	  
    	  // Add the full search term to the solr query
    	  return returnValueBuilder.toString();
      }
      else {
    	  if ("*".equals(term))
          	term = "[* TO *]";
          return index+":("+term+")";
      }

    }
    else {
        if ("*".equals(term))
        	term = "[* TO *]";
    	return index+":"+"("+term+")";
    }
  }

  /**public Map<String,String> getExtraResponseProps() 
  {
    return extra_response_props;
  }

  public void setExtraResponseProps(Map<String,String> extra_response_props) 
  {
    this.extra_response_props = extra_response_props;
  }
  **/
  private String createSortQuery(String sortBy) 
  {
	  if (sortBy == null || "".equals(sortBy.trim()))
		  return "";
	  
	  StringBuffer query   = new StringBuffer();
	  String[] fields      = sortBy.split(" ");
	  for (String f: fields) 
      {
	      if(query.length()>0)
	          query.append(", ");
	      
	      String[] params = f.split("/");
	      query.append(params[0]);
          if (params.length >= 2 && "sort.descending".equals(params[1])) {
        	  log.debug("adding descending");
        	  query.append(" desc");
          } else {
              query.append(" asc");
              log.debug("adding ascending - params[1] = " + params[1]);
          }
          
      }
	 // System.out.println("Sort query is "+query.toString());
	  return query.toString();
	  
  }
  
	private String SolrDocToString(org.apache.solr.common.SolrDocument doc) 
	{
	    StringBuilder sb = new StringBuilder();
	    sb.append("<doc>");
	    for (String key : doc.keySet()) 
	    {
	      outputElem(sb, doc.get(key), key, true);
	    }
	    sb.append("</doc>");
	    return sb.toString();
	}
	
	
	private void appendSolrFacet(FacetField ff, StringBuilder sb, long record_count)
	{
	    if(ff.getValues()!=null)
	    {
	        List<Count> count_list=new ArrayList<Count>();
	        Iterator<Count> i = ff.getValues().iterator();
            while(i.hasNext())
            {
                Count c = i.next();
                if(c.getCount()==record_count) // ignore if facet count = record count
                    continue;
                
                String name = c.getName();
                
                if(name.length()>128)
                    continue;
                
                if(name.indexOf("\"")!=-1) // for the mo ignore anythign containing(")
                    continue;
                
                if(name.indexOf("\'")!=-1) // for the mo ignore anythign containing(')
                    continue;
                
                count_list.add(c);
            }
            sb.append("<lst name=\"");
            sb.append(ff.getName());
            sb.append("\" valueCount=\"");
            sb.append(count_list.size());
            sb.append("\">");
           
	        Iterator<Count> count_i = count_list.iterator();
            while(count_i.hasNext())
            {
                Count c        = count_i.next();
                String name    = c.getName();
             
                sb.append("<facet count=\"");
                sb.append(c.getCount());
                sb.append("\">");
               
                if(name.indexOf("&")!=-1)
                    name=name.replaceAll("\\&","&amp;");
                if (name.indexOf("<")!=-1)
                    name=name.replaceAll("<","&lt;");
                
                sb.append(name);
               
                sb.append("</facet>");
            }        
    	        
    	    sb.append("</lst>");
          
	    }
	    
	    
	}
	
    private void outputElem(StringBuilder sb, Object value, String name, boolean outputName) {
	     if ( value != null  && ( name != null ) && ( ! name.startsWith("_tier") ) ) {

                 // Push out a geo_distance str for backwards compatibility
                if ( name.equalsIgnoreCase("distance") ) {
                    outputValue(sb,value,"geo_distance","str");
                }
	       if ( value instanceof String ) {
	         outputValue(sb,value,name,"str");
	       }
	       else if ( value instanceof java.lang.Boolean ) {
	         outputValue(sb,value,name,"bool");
	       }
	       else if ( value instanceof java.lang.Integer ) {
	         outputValue(sb,value,name,"int");
	       }
	       else if ( value instanceof java.util.Date ) {
	         outputValue(sb,value,name,"date");
	       }
	       else if ( value instanceof java.util.ArrayList ) {
	         sb.append("<arr");
	         if ( name != null ) {
	           sb.append(" name=\"");
	           sb.append(name);
	           sb.append("\"");
	         }
	         sb.append(">");
	         @SuppressWarnings("unchecked")
			 ArrayList<Object> valuesArray = (ArrayList<Object>)value;
	         for ( Object elem : valuesArray) {
	           outputElem(sb, elem, name, false);
	         }
	         sb.append("</arr>");
	        // outputValue(sb,value,name,"date");
	       }
	       else {
	         sb.append("<!--");
	         sb.append(value.getClass().getName());
	         sb.append("-->");
	       }
	     }
	   }

	   private void outputValue(StringBuilder sb, Object value, String name, String element) 
	   {
	     boolean do_arr_exception = array_element_exceptions.contains(name);

	     if ( do_arr_exception ) {
	       sb.append("<arr>");
	     }

	     sb.append("<");
	     sb.append(element);
	     if ( name != null ) {
	       sb.append(" name=\"");
	       sb.append(name);
	       sb.append("\"");
	     }
	     sb.append(">");
	     sb.append(value.toString().replaceAll("\\&","&amp;").replaceAll("<","&lt;"));
	     sb.append("</");
	     sb.append(element);
	     sb.append(">");

	     if ( do_arr_exception ) {
	       sb.append("</arr>");
	     }

	   }
}
