org.codice.solr.factory.SolrClientFactory.java Source code

Java tutorial

Introduction

Here is the source code for org.codice.solr.factory.SolrClientFactory.java

Source

/**
 * Copyright (c) Codice Foundation
 * <p>
 * This 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 any later version.
 * <p>
 * This program 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. A copy of the GNU Lesser General Public License
 * is distributed along with this program and can be found at
 * <http://www.gnu.org/licenses/lgpl.html>.
 */
package org.codice.solr.factory;

import java.io.FileInputStream;
import java.io.IOException;
import java.nio.file.Paths;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

import javax.net.ssl.SSLContext;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.time.DurationFormatUtils;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.HttpRequestRetryHandler;
import org.apache.http.client.methods.HttpHead;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLContexts;
import org.apache.http.impl.client.BasicCookieStore;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.client.HttpClients;
import org.apache.solr.client.solrj.SolrClient;
import org.apache.solr.client.solrj.SolrServerException;
import org.apache.solr.client.solrj.impl.HttpSolrClient;
import org.apache.solr.client.solrj.request.CoreAdminRequest;
import org.apache.solr.client.solrj.response.CoreAdminResponse;
import org.codice.ddf.configuration.SystemBaseUrl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.util.concurrent.Futures;

/**
 * Factory that creates {@link org.apache.solr.client.solrj.SolrClient} instances.
 */
public class SolrClientFactory {

    protected static final Logger LOGGER = LoggerFactory.getLogger(SolrClientFactory.class);

    private static ExecutorService pool = getThreadPool();

    public static final String DEFAULT_CORE_NAME = "core1";

    public static final List<String> DEFAULT_PROTOCOLS = Collections
            .unmodifiableList(Arrays.asList(StringUtils.split(System.getProperty("https.protocols"), ",")));

    public static final List<String> DEFAULT_CIPHER_SUITES = Collections
            .unmodifiableList(Arrays.asList(StringUtils.split(System.getProperty("https.cipherSuites"), ",")));

    public static final String DEFAULT_SCHEMA_XML = "schema.xml";

    public static final String DEFAULT_SOLRCONFIG_XML = "solrconfig.xml";

    private static final Integer MAX_RETRY_COUNT = 11;

    private static final String THREAD_POOL_DEFAULT_SIZE = "128";

    private static ExecutorService getThreadPool() throws NumberFormatException {
        Integer threadPoolSize = Integer
                .parseInt(System.getProperty("org.codice.ddf.system.threadPoolSize", THREAD_POOL_DEFAULT_SIZE));
        return Executors.newFixedThreadPool(threadPoolSize);
    }

    public static String getDefaultHttpsAddress() {
        return SystemBaseUrl.constructUrl("https", "/solr");
    }

    public static String getDefaultHttpAddress() {
        return SystemBaseUrl.constructUrl("http", "/solr");
    }

    /**
     * Creates an {@link org.apache.solr.client.solrj.SolrClient} with the default http address
     * url.
     *
     * @return SolrClient
     */
    static SolrClient getHttpSolrClient() {
        return new HttpSolrClient(getDefaultHttpAddress());
    }

    public static Future<SolrClient> getHttpSolrClient(String url) {
        return getHttpSolrClient(url, DEFAULT_CORE_NAME, null);
    }

    public static Future<SolrClient> getHttpSolrClient(String url, String coreName) {
        return getHttpSolrClient(url, coreName, null);
    }

    public static Future<SolrClient> getHttpSolrClient(String url, String coreName, String configFile) {
        if (StringUtils.isBlank(url)) {
            url = SystemBaseUrl.constructUrl("/solr");
        }

        String coreUrl = url + "/" + coreName;
        SolrClient client;
        try {
            client = getSolrClient(url, coreName, configFile, coreUrl);
        } catch (Exception ex) {
            LOGGER.info("Returning future for HTTP Solr client ({})", coreName);
            LOGGER.debug("Failed to create Solr client (" + coreName + ")", ex);
            return pool.submit(new SolrClientFetcher(url, coreName, configFile, coreUrl));
        }

        LOGGER.info("Created HTTP Solr client ({})", coreName);
        return Futures.immediateFuture(client);
    }

    private static SolrClient getSolrClient(String url, String coreName, String configFile, String coreUrl)
            throws IOException, SolrServerException {
        SolrClient client;
        if (StringUtils.startsWith(url, "https")) {
            createSolrCore(url, coreName, configFile, getHttpClient(false));
            client = new HttpSolrClient(coreUrl, getHttpClient(true));
        } else {
            createSolrCore(url, coreName, configFile, null);
            client = new HttpSolrClient(coreUrl);
        }
        return client;
    }

    private static CloseableHttpClient getHttpClient(boolean retryRequestsOnError) {
        SSLConnectionSocketFactory sslConnectionSocketFactory = new SSLConnectionSocketFactory(getSslContext(),
                getProtocols(), getCipherSuites(), SSLConnectionSocketFactory.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER);
        HttpRequestRetryHandler solrRetryHandler = new SolrHttpRequestRetryHandler();

        HttpClientBuilder builder = HttpClients.custom().setSSLSocketFactory(sslConnectionSocketFactory)
                .setDefaultCookieStore(new BasicCookieStore()).setMaxConnTotal(128).setMaxConnPerRoute(32);

        if (retryRequestsOnError) {
            builder.setRetryHandler(solrRetryHandler);
        }

        return builder.build();
    }

    private static String[] getProtocols() {
        if (System.getProperty("https.protocols") != null) {
            return StringUtils.split(System.getProperty("https.protocols"), ",");
        } else {
            return DEFAULT_PROTOCOLS.toArray(new String[DEFAULT_PROTOCOLS.size()]);
        }
    }

    private static String[] getCipherSuites() {
        if (System.getProperty("https.cipherSuites") != null) {
            return StringUtils.split(System.getProperty("https.cipherSuites"), ",");
        } else {
            return DEFAULT_CIPHER_SUITES.toArray(new String[DEFAULT_CIPHER_SUITES.size()]);
        }
    }

    private static SSLContext getSslContext() {
        if (System.getProperty("javax.net.ssl.keyStore") == null
                || System.getProperty("javax.net.ssl.keyStorePassword") == null
                || System.getProperty("javax.net.ssl.trustStore") == null
                || System.getProperty("javax.net.ssl.trustStorePassword") == null) {
            throw new IllegalArgumentException("KeyStore and TrustStore system properties must be" + " set.");
        }

        KeyStore trustStore = getKeyStore(System.getProperty("javax.net.ssl.trustStore"),
                System.getProperty("javax.net.ssl.trustStorePassword"));
        KeyStore keyStore = getKeyStore(System.getProperty("javax.net.ssl.keyStore"),
                System.getProperty("javax.net.ssl.keyStorePassword"));

        SSLContext sslContext = null;

        try {
            sslContext = SSLContexts.custom()
                    .loadKeyMaterial(keyStore, System.getProperty("javax.net.ssl.keyStorePassword").toCharArray())
                    .loadTrustMaterial(trustStore).useTLS().build();
        } catch (UnrecoverableKeyException | NoSuchAlgorithmException | KeyStoreException
                | KeyManagementException e) {
            LOGGER.error("Unable to create secure HttpClient", e);
            return null;
        }

        sslContext.getDefaultSSLParameters().setNeedClientAuth(true);
        sslContext.getDefaultSSLParameters().setWantClientAuth(true);

        return sslContext;
    }

    private static KeyStore getKeyStore(String location, String password) {
        LOGGER.debug("Loading keystore from {}", location);
        KeyStore keyStore = null;

        try (FileInputStream storeStream = new FileInputStream(location)) {
            keyStore = KeyStore.getInstance(System.getProperty("javax.net.ssl.keyStoreType"));
            keyStore.load(storeStream, password.toCharArray());
        } catch (CertificateException | IOException | NoSuchAlgorithmException | KeyStoreException e) {
            LOGGER.error("Unable to load keystore at " + location, e);
        }

        return keyStore;
    }

    private static void createSolrCore(String url, String coreName, String configFileName, HttpClient httpClient)
            throws IOException, SolrServerException {
        HttpSolrClient client;
        if (httpClient != null) {
            client = new HttpSolrClient(url, httpClient);
        } else {
            client = new HttpSolrClient(url);
        }

        HttpResponse ping = client.getHttpClient().execute(new HttpHead(url));
        if (ping != null && ping.getStatusLine().getStatusCode() == 200) {
            if (!solrCoreExists(client, coreName)) {
                LOGGER.debug("Creating Solr core {}", coreName);

                String configFile = StringUtils.defaultIfBlank(configFileName, DEFAULT_SOLRCONFIG_XML);

                String instanceDir = Paths.get(System.getProperty("karaf.home"), "data", "solr", coreName)
                        .toString();

                CoreAdminRequest.createCore(coreName, instanceDir, client, configFile, DEFAULT_SCHEMA_XML);
            } else {
                LOGGER.debug("Solr core ({}) already exists - reloading it", coreName);
                CoreAdminRequest.reloadCore(coreName, client);
            }
        } else {
            LOGGER.debug("Unable to ping Solr core {}", coreName);
            throw new SolrServerException("Unable to ping Solr core");
        }
    }

    private static boolean solrCoreExists(SolrClient client, String coreName) {
        try {
            CoreAdminResponse response = CoreAdminRequest.getStatus(coreName, client);
            return response.getCoreStatus(coreName).get("instanceDir") != null;
        } catch (Exception e) {
            LOGGER.debug("Exception getting " + coreName + " core status", e);
            return false;
        }
    }

    private static class SolrClientFetcher implements Callable<SolrClient> {
        private final String url;

        private final String coreName;

        private final String configFile;

        private final String coreUrl;

        private int retryCount;

        public SolrClientFetcher(String url, String coreName, String configFile, String coreUrl) {
            this.url = url;
            this.coreName = coreName;
            this.configFile = configFile;
            this.coreUrl = coreUrl;
            this.retryCount = 0;
        }

        @Override
        public SolrClient call() throws Exception {
            while (true) {
                int retryIndex = retryCount + 1;
                try {
                    LOGGER.info("Retry {} to create Solr client for ({})", retryIndex, coreName);
                    SolrClient client = getSolrClient(url, coreName, configFile, coreUrl);
                    LOGGER.info("Future for HTTP Solr client ({}) finished", coreName);
                    return client;
                } catch (Exception e) {
                    retryCount = Math.min(retryCount + 1, MAX_RETRY_COUNT);
                    long retrySleepMillis = (long) Math.pow(2, Math.min(retryCount, MAX_RETRY_COUNT)) * 50;
                    LOGGER.info("Failed retry {} to create Solr client ({}), trying again in {}", retryIndex,
                            coreName, DurationFormatUtils.formatDurationWords(retrySleepMillis, true, true));
                    LOGGER.debug("Retry failed", e);
                    Thread.sleep(retrySleepMillis);
                }
            }
        }
    }
}