edu.cornell.mannlib.vitro.webapp.servlet.setup.SolrSmokeTest.java Source code

Java tutorial

Introduction

Here is the source code for edu.cornell.mannlib.vitro.webapp.servlet.setup.SolrSmokeTest.java

Source

/* $This file is distributed under the terms of the license in /doc/license.txt$ */

package edu.cornell.mannlib.vitro.webapp.servlet.setup;

import java.io.IOException;
import java.net.ConnectException;
import java.net.MalformedURLException;
import java.net.SocketTimeoutException;
import java.net.URL;
import java.net.UnknownHostException;

import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.http.HttpException;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;

import edu.cornell.mannlib.vitro.webapp.config.ConfigurationProperties;
import edu.cornell.mannlib.vitro.webapp.startup.StartupStatus;
import edu.cornell.mannlib.vitro.webapp.utils.threads.VitroBackgroundThread;

/**
 * Spin off a thread that will try to connect to Solr.
 * 
 * We need to do this in a separate thread because if Solr is in the same Tomcat
 * instance as Vitro, it may not be initialized until after Vitro is
 * initialized. Which is to say, after this Listener has run.
 * 
 * If we can't connect to Solr, add a Warning item to the StartupStatus.
 */
public class SolrSmokeTest implements ServletContextListener {
    private static final Log log = LogFactory.getLog(SolrSmokeTest.class);

    /*
     * We don't want to treat socket timeout as a non-recoverable error like the
     * other exceptions. So pretend there's a status code for it instead.
     */
    private static final int SOCKET_TIMEOUT_STATUS = -500;

    @Override
    public void contextInitialized(ServletContextEvent sce) {
        final StartupStatus ss = StartupStatus.getBean(sce.getServletContext());

        String solrUrlString = ConfigurationProperties.getBean(sce).getProperty("vitro.local.solr.url", "");
        if (solrUrlString.isEmpty()) {
            ss.fatal(this, "Can't connect to Solr search engine. " + "runtime.properties must contain a value for "
                    + "vitro.local.solr.url");
            return;
        }

        URL solrUrl = null;

        try {
            solrUrl = new URL(solrUrlString);
        } catch (MalformedURLException e) {
            ss.fatal(this, "Can't connect to Solr search engine. " + "The value for vitro.local.solr.url "
                    + "in runtime.properties is not a valid URL: '" + solrUrlString + "'", e);
        }

        ss.info(this, "Starting thread for Solr test.");
        new SolrSmokeTestThread(this, solrUrl, ss).start();
    }

    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        // nothing to tear down.
    }

    private static class SolrSmokeTestThread extends VitroBackgroundThread {
        private final SolrSmokeTest listener;
        private final URL solrUrl;
        private final StartupStatus ss;

        public SolrSmokeTestThread(SolrSmokeTest listener, URL solrUrl, StartupStatus ss) {
            super("SolrSmokeTest");
            this.listener = listener;
            this.solrUrl = solrUrl;
            this.ss = ss;
        }

        /**
         * Try to connect until we suceed, until we detect an unrecoverable
         * error, or until we have failed 3 times.
         */
        @Override
        public void run() {
            try {
                new SolrHomePager(solrUrl).connect();
                reportSuccess();
            } catch (SolrProblemException e) {
                reportProblem(e);
                return;
            }

            try {
                new SolrPinger(solrUrl).ping();
                reportGoodPing();
            } catch (SolrProblemException e) {
                reportPingProblem(e);
                return;
            }
        }

        private void reportSuccess() {
            ss.info(listener, "Successfully connected to the Solr search server.");
        }

        private void reportGoodPing() {
            ss.info(listener, "The Solr search server responded to a 'ping'.");
        }

        private void reportProblem(SolrProblemException e) {
            int status = e.getStatusCode();
            Throwable cause = e.getCause();

            if (status == SOCKET_TIMEOUT_STATUS) {
                warnSocketTimeout();
            } else if (status != 0) {
                warnBadHttpStatus(status);
            } else if (cause instanceof HttpException) {
                warnProtocolViolation((HttpException) cause);
            } else if (cause instanceof UnknownHostException) {
                warnUnknownHost((UnknownHostException) cause);
            } else if (cause instanceof ConnectException) {
                warnConnectionRefused((ConnectException) cause);
            } else if (cause instanceof IOException) {
                warnTransportError((IOException) cause);
            } else {
                warnUnknownProblem(e);
            }
        }

        private void reportPingProblem(SolrProblemException e) {
            ss.warning(listener, "The Solr search engine did not respond to a 'ping' request", e);
        }

        private void warnSocketTimeout() {
            ss.warning(listener,
                    "Can't connect to the Solr search engine. " + "The socket connection has repeatedly timed out. "
                            + "Check the value of vitro.local.solr.url in "
                            + "runtime.properties. Is Solr responding at that URL?");
        }

        private void warnBadHttpStatus(int status) {
            ss.warning(listener,
                    "Can't connect to the Solr search engine. " + "The Solr server returned a status code of "
                            + status + ". Check the value of vitro.local.solr.url in " + "runtime.properties.");
        }

        private void warnProtocolViolation(HttpException e) {
            ss.warning(listener, "Can't connect to the Solr search engine. " + "Detected a protocol violation: "
                    + e.getMessage(), e);
        }

        private void warnUnknownHost(UnknownHostException e) {
            ss.warning(listener,
                    "Can't connect to the Solr search engine. '" + e.getMessage() + "' is an unknown host."
                            + "Check the value of vitro.local.solr.url in " + "runtime.properties.",
                    e);
        }

        private void warnConnectionRefused(ConnectException e) {
            ss.warning(listener,
                    "Can't connect to the Solr search engine. " + "The host refused the connection. "
                            + "Is it possible that the port number is incorrect? "
                            + "Check the value of vitro.local.solr.url in " + "runtime.properties.",
                    e);
        }

        private void warnTransportError(IOException e) {
            ss.warning(listener,
                    "Can't connect to the Solr search engine. " + "Detected a transport error: " + e.getMessage(),
                    e);
        }

        private void warnUnknownProblem(SolrProblemException e) {
            ss.warning(listener,
                    "Can't connect to the Solr search engine. " + "Unrecognized error: " + e.getMessage(), e);
        }

    }

    /**
     * <pre>
     * This gets a little tricky. Try to connect until:
     *   -- we suceed
     *   -- we detect a non-recoverable error
     *   -- we fail three times
     * </pre>
     */
    private static class SolrHomePager {
        private static final long SLEEP_INTERVAL = 20000; // 20 seconds

        private final URL solrUrl;
        private final HttpClient httpClient = new DefaultHttpClient();

        private int statusCode;

        public SolrHomePager(URL solrUrl) {
            this.solrUrl = solrUrl;
        }

        public void connect() throws SolrProblemException {
            tryToConnect();

            if (!isDone()) {
                sleep();
                tryToConnect();
            }

            if (!isDone()) {
                sleep();
                tryToConnect();
            }

            if (statusCode != HttpStatus.SC_OK) {
                throw new SolrProblemException(statusCode);
            }
        }

        private void tryToConnect() throws SolrProblemException {
            try {
                HttpGet method = new HttpGet(solrUrl.toExternalForm());
                SolrSmokeTest.log.debug("Trying to connect to Solr");
                HttpResponse response = httpClient.execute(method);
                statusCode = response.getStatusLine().getStatusCode();
                SolrSmokeTest.log.debug("HTTP status was " + statusCode);
            } catch (SocketTimeoutException e) {
                // Catch the exception so we can retry this.
                // Save the status so we know why we failed.
                statusCode = SolrSmokeTest.SOCKET_TIMEOUT_STATUS;
            } catch (Exception e) {
                throw new SolrProblemException(e);
            }
        }

        /**
         * Stop trying to connect if we succeed, or if we receive an error that
         * won't change on retry.
         */
        private boolean isDone() {
            return (statusCode == HttpStatus.SC_OK) || (statusCode == HttpStatus.SC_FORBIDDEN);
        }

        private void sleep() {
            try {
                Thread.sleep(SLEEP_INTERVAL);
            } catch (InterruptedException e) {
                e.printStackTrace(); // Should never happen
            }
        }

    }

    /**
     * Issue a "ping" to Solr. If we get here, we've already established
     * contact, so any error is a fatal one.
     */
    private static class SolrPinger {
        private final URL solrUrl;
        private final HttpClient httpClient = new DefaultHttpClient();

        public SolrPinger(URL solrUrl) {
            this.solrUrl = solrUrl;
        }

        public void ping() throws SolrProblemException {
            try {
                HttpGet method = new HttpGet(solrUrl.toExternalForm() + "/admin/ping");
                SolrSmokeTest.log.debug("Trying to ping Solr");
                HttpResponse response = httpClient.execute(method);
                SolrSmokeTest.log.debug("Finished pinging Solr");
                int statusCode = response.getStatusLine().getStatusCode();
                if (statusCode != HttpStatus.SC_OK) {
                    throw new SolrProblemException(statusCode);
                }
            } catch (IOException e) {
                throw new SolrProblemException(e);
            }
        }
    }

    private static class SolrProblemException extends Exception {
        private final int statusCode;

        SolrProblemException(int statusCode) {
            super("HTTP status code = " + statusCode);
            this.statusCode = statusCode;
        }

        SolrProblemException(Throwable cause) {
            super(cause);
            this.statusCode = 0;
        }

        int getStatusCode() {
            return this.statusCode;
        }
    }
}