Mark Rogers 0dac120b1c Merged HEAD-BUG-FIX (5.0/Cloud) to HEAD (5.0/Cloud)
84822: Merged PLATFORM1 (5.0/Cloud) to HEAD-BUG-FIX (5.0/Cloud)
      82523: Fix for     ACE-1044  SOLR 4 - Back up and recovery
      - added baseUrl configuration
      - improved admin


git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@85181 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
2014-09-20 08:42:16 +00:00

395 lines
11 KiB
Java

/*
* 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 <http://www.gnu.org/licenses/>.
*/
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.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);
// 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<String> 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<String> cores;
SolrTracker(Scheduler scheduler)
{
this.scheduler = scheduler;
ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
writeLock = lock.writeLock();
cores = new ArrayList<String>(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<Object> results = response.getResponse();
@SuppressWarnings("unchecked")
NamedList<Object> report = (NamedList<Object>)results.get("status");
Iterator<Map.Entry<String, Object>> coreIterator = report.iterator();
List<String> cores = new ArrayList<String>(report.size());
while(coreIterator.hasNext())
{
Map.Entry<String, Object> core = coreIterator.next();
cores.add(core.getKey());
}
registerCores(cores);
setSolrActive(true);
}
else
{
setSolrActive(false);
}
}
void setSolrActive(boolean active)
{
boolean statusChanged = false;
try
{
writeLock.lock();
try
{
if(solrActive != active)
{
solrActive = active;
statusChanged = true;
}
}
finally
{
writeLock.unlock();
}
if(statusChanged)
{
// do this outside the write lock
if(solrActive)
{
stopTimer();
applicationEventPublisher.publishEvent(new SolrActiveEvent(this));
}
else
{
startTimer();
applicationEventPublisher.publishEvent(new SolrInactiveEvent(this));
}
}
}
catch(Exception e)
{
throw new AlfrescoRuntimeException("", e);
}
}
boolean isSolrActive()
{
return solrActive;
}
protected void setupTimer()
{
try
{
final String jobName = "SolrWatcher";
final String jobGroup = "Solr";
// If a Quartz job already exists with this name and group then we want to replace it.
// It is not expected that this will occur during production, but it is possible during automated testing
// where application contexts could be rebuilt between test cases, leading to multiple creations of
// equivalent Quartz jobs. Quartz disallows the scheduling of multiple jobs with the same name and group.
JobDetail existingJob = scheduler.getJobDetail(jobName, jobGroup);
if (existingJob != null)
{
scheduler.deleteJob(jobName, jobGroup);
}
JobDetail job = new JobDetail(jobName, jobGroup, SOLRWatcherJob.class);
JobDataMap jobDataMap = new JobDataMap();
jobDataMap.put("SOLR_TRACKER", this);
job.setJobDataMap(jobDataMap);
trigger = new CronTrigger("SolrWatcherTrigger", jobGroup, solrPingCronExpression);
scheduler.scheduleJob(job, trigger);
}
catch(Exception e)
{
throw new AlfrescoRuntimeException("Unable to set up SOLRTracker timer", e);
}
}
protected void startTimer() throws SchedulerException
{
scheduler.resumeTrigger(trigger.getName(), trigger.getGroup());
}
protected void stopTimer() throws SchedulerException
{
scheduler.pauseTrigger(trigger.getName(), trigger.getGroup());
}
void registerCores(List<String> cores)
{
writeLock.lock();
try
{
this.cores = cores;
}
finally
{
writeLock.unlock();
}
}
@SuppressWarnings("unchecked")
List<String> getRegisteredCores()
{
writeLock.lock();
try
{
return (cores != null ? cores : Collections.EMPTY_LIST);
}
finally
{
writeLock.unlock();
}
}
}
/* (non-Javadoc)
* @see org.springframework.beans.factory.DisposableBean#destroy()
*/
@Override
public void destroy() throws Exception
{
solrTracker.stopTimer();
}
}