org.alfresco.repo.solr.SOLRAdminClient.java Source code

Java tutorial

Introduction

Here is the source code for org.alfresco.repo.solr.SOLRAdminClient.java

Source

/*
 * #%L
 * Alfresco Repository
 * %%
 * Copyright (C) 2005 - 2016 Alfresco Software Limited
 * %%
 * This file is part of the Alfresco software. 
 * If the software was purchased under a paid Alfresco license, the terms of 
 * the paid license agreement will prevail.  Otherwise, the software is 
 * provided under the following open source license terms:
 * 
 * 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/>.
 * #L%
 */
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<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();
    }

}