/*
 * Copyright (C) 2005-2014 Alfresco Software Limited.
 *
 * This file is part of Alfresco
 *
 * Alfresco is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Alfresco 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 Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with Alfresco. If not, see .
 */
package org.alfresco.repo.solr;
import java.net.MalformedURLException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock;
import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.httpclient.HttpClientFactory;
import org.alfresco.util.ParameterCheck;
import org.apache.commons.httpclient.Credentials;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.UsernamePasswordCredentials;
import org.apache.commons.httpclient.auth.AuthScope;
import org.apache.solr.client.solrj.SolrServerException;
import org.apache.solr.client.solrj.impl.CommonsHttpSolrServer;
import org.apache.solr.client.solrj.impl.XMLResponseParser;
import org.apache.solr.client.solrj.response.QueryResponse;
import org.apache.solr.common.params.ModifiableSolrParams;
import org.apache.solr.common.util.NamedList;
import org.quartz.CronTrigger;
import org.quartz.JobDataMap;
import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.Trigger;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
/**
 * Provides an interface to the Solr admin APIs, used by the Alfresco Enterprise JMX layer.
 * Also tracks whether Solr is available, sending Spring events when its availability changes.
 * 
 * @since 4.0
 *
 */
public class SOLRAdminClient implements ApplicationEventPublisherAware, DisposableBean
{
	private String solrHost;
	private int solrPort;
	private int solrSSLPort;
	private String solrUrl;
	private String solrUser;
	private String solrPassword;
	private String solrPingCronExpression;
	private String baseUrl;
	private CommonsHttpSolrServer server;
	private int solrConnectTimeout = 30000; // ms
	private ApplicationEventPublisher applicationEventPublisher;
	private SolrTracker solrTracker;
	
	private HttpClientFactory httpClientFactory;
    private Scheduler scheduler;
	public SOLRAdminClient()
	{
	}
	public void setSolrHost(String solrHost)
	{
		this.solrHost = solrHost;
	}
	
	public void setSolrPort(String solrPort)
	{
		this.solrPort = Integer.parseInt(solrPort);
	}
	
	public void setSolrsslPort(int solrSSLPort)
	{
		this.solrSSLPort = solrSSLPort;
	}
	public void setSolrUser(String solrUser)
	{
		this.solrUser = solrUser;
	}
	public void setSolrPassword(String solrPassword)
	{
		this.solrPassword = solrPassword;
	}
	
	public void setSolrConnectTimeout(String solrConnectTimeout)
	{
		this.solrConnectTimeout = Integer.parseInt(solrConnectTimeout);
	}
	@Override
	public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher)
	{
		this.applicationEventPublisher = applicationEventPublisher;
	}
	
	public void setSolrPingCronExpression(String solrPingCronExpression)
    {
        this.solrPingCronExpression = solrPingCronExpression;
    }
    public void setHttpClientFactory(HttpClientFactory httpClientFactory)
	{
		this.httpClientFactory = httpClientFactory;
	}
   
    public void setBaseUrl(String baseUrl)
    {
        this.baseUrl = baseUrl;
    }
    /**
     * @param scheduler the scheduler to set
     */
    public void setScheduler(Scheduler scheduler)
    {
        this.scheduler = scheduler;
    }
    public void init()
	{
    	ParameterCheck.mandatory("solrHost", solrHost);
    	ParameterCheck.mandatory("solrPort", solrPort);
    	ParameterCheck.mandatory("solrUser", solrUser);
    	ParameterCheck.mandatory("solrPassword", solrPassword);
    	ParameterCheck.mandatory("solrPingCronExpression", solrPingCronExpression);
    	ParameterCheck.mandatory("solrConnectTimeout", solrConnectTimeout);
		try
		{
	    	StringBuilder sb = new StringBuilder();
	    	sb.append(httpClientFactory.isSSL() ? "https://" : "http://");
	    	sb.append(solrHost);
	    	sb.append(":");
	    	sb.append(httpClientFactory.isSSL() ? solrSSLPort: solrPort);
	    	sb.append(baseUrl);
			this.solrUrl = sb.toString();
			HttpClient httpClient = httpClientFactory.getHttpClient();
			server = new CommonsHttpSolrServer(solrUrl, httpClient);
			server.setParser(new XMLResponseParser());
			// TODO remove credentials because we're using SSL?
			Credentials defaultcreds = new UsernamePasswordCredentials(solrUser, solrPassword); 
			server.getHttpClient().getState().setCredentials(new AuthScope(solrHost, solrPort, AuthScope.ANY_REALM), 
					defaultcreds);
			server.setConnectionTimeout(solrConnectTimeout);
			server.setSoTimeout(20000);
			this.solrTracker = new SolrTracker(scheduler);
		}
		catch(MalformedURLException e)
		{
			throw new AlfrescoRuntimeException("Cannot initialise Solr admin http client", e);
		}
	}
	public QueryResponse basicQuery(ModifiableSolrParams params)
	{
    	try
    	{
		    QueryResponse response = server.query(params);
		    return response;
		}
		catch(SolrServerException e)
		{
			return null;
		}
	}
	public QueryResponse query(ModifiableSolrParams params) throws SolrServerException
	{
    	try
    	{
		    QueryResponse response = server.query(params);
		    if(response.getStatus() != 0)
		    {
		    	solrTracker.setSolrActive(false);
		    }
		    return response;
		}
		catch(SolrServerException e)
		{
			solrTracker.setSolrActive(false);
			throw e;
		}
	}
	
	public List getRegisteredCores()
	{
		return solrTracker.getRegisteredCores();
	}
	
	/**
	 * Tracks the availability of Solr.
	 * 
	 * @since 4.0
	 *
	 */
	class SolrTracker
	{
	    private final WriteLock writeLock;
		private boolean solrActive = false;
	    private Scheduler scheduler = null;
	    private Trigger trigger;
	    private List cores;
		SolrTracker(Scheduler scheduler)
		{
		    this.scheduler = scheduler;
	        ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
	        writeLock = lock.writeLock();
	        
	        cores = new ArrayList(5);
	    	setupTimer();
		}
		
		protected void pingSolr()
		{
		    ModifiableSolrParams params = new ModifiableSolrParams();
		    params.set("qt", "/admin/cores");
		    params.set("action", "STATUS");
			
		    QueryResponse response = basicQuery(params);
		    if(response != null && response.getStatus() == 0)
		    {
			    NamedList