com.sonyericsson.hudson.plugins.gerrit.trigger.GerritServer.java Source code

Java tutorial

Introduction

Here is the source code for com.sonyericsson.hudson.plugins.gerrit.trigger.GerritServer.java

Source

/*
 *  The MIT License
 *
 *  Copyright 2010 Sony Mobile Communications Inc. All rights reserved.
 *  Copyright 2013 Ericsson.
 *
 *  Permission is hereby granted, free of charge, to any person obtaining a copy
 *  of this software and associated documentation files (the "Software"), to deal
 *  in the Software without restriction, including without limitation the rights
 *  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 *  copies of the Software, and to permit persons to whom the Software is
 *  furnished to do so, subject to the following conditions:
 *
 *  The above copyright notice and this permission notice shall be included in
 *  all copies or substantial portions of the Software.
 *
 *  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 *  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 *  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 *  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 *  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 *  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 *  THE SOFTWARE.
 */
package com.sonyericsson.hudson.plugins.gerrit.trigger;

import static com.sonymobile.tools.gerrit.gerritevents.watchdog.WatchTimeExceptionData.Time.MAX_HOUR;
import static com.sonymobile.tools.gerrit.gerritevents.watchdog.WatchTimeExceptionData.Time.MAX_MINUTE;
import static com.sonymobile.tools.gerrit.gerritevents.watchdog.WatchTimeExceptionData.Time.MIN_HOUR;
import static com.sonymobile.tools.gerrit.gerritevents.watchdog.WatchTimeExceptionData.Time.MIN_MINUTE;
import static com.sonyericsson.hudson.plugins.gerrit.trigger.utils.StringUtil.PLUGIN_IMAGES_URL;
import hudson.Extension;
import hudson.Functions;
import hudson.RelativePath;
import hudson.model.AbstractProject;
import hudson.model.Job;
import hudson.model.Action;
import hudson.model.Describable;
import hudson.model.Failure;
import hudson.model.Descriptor;
import hudson.model.Hudson;
import hudson.util.FormValidation;
import hudson.util.ListBoxModel;
import hudson.util.Secret;
import hudson.util.ListBoxModel.Option;

import java.io.File;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;
import java.util.Map.Entry;
import java.util.concurrent.Callable;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

import javax.servlet.ServletException;

import jenkins.model.Jenkins;
import net.sf.json.JSONObject;

import org.apache.commons.lang.CharEncoding;
import org.apache.http.HttpResponse;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.apache.http.impl.client.HttpClients;
import org.jvnet.localizer.ResourceBundleHolder;
import org.kohsuke.stapler.QueryParameter;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
import org.kohsuke.stapler.bind.JavaScriptMethod;
import org.kohsuke.stapler.export.Exported;
import org.kohsuke.stapler.export.ExportedBean;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.sonymobile.tools.gerrit.gerritevents.ConnectionListener;
import com.sonymobile.tools.gerrit.gerritevents.GerritDefaultValues;
import com.sonymobile.tools.gerrit.gerritevents.GerritEventListener;
import com.sonymobile.tools.gerrit.gerritevents.GerritHandler;
import com.sonymobile.tools.gerrit.gerritevents.GerritConnection;
import com.sonymobile.tools.gerrit.gerritevents.dto.GerritEvent;
import com.sonymobile.tools.gerrit.gerritevents.dto.rest.Notify;
import com.sonymobile.tools.gerrit.gerritevents.ssh.Authentication;
import com.sonymobile.tools.gerrit.gerritevents.ssh.SshAuthenticationException;
import com.sonymobile.tools.gerrit.gerritevents.ssh.SshConnectException;
import com.sonymobile.tools.gerrit.gerritevents.ssh.SshConnection;
import com.sonymobile.tools.gerrit.gerritevents.ssh.SshConnectionFactory;
import com.sonymobile.tools.gerrit.gerritevents.ssh.SshUtil;
import com.sonymobile.tools.gerrit.gerritevents.watchdog.WatchTimeExceptionData;
import com.sonyericsson.hudson.plugins.gerrit.trigger.config.Config;
import com.sonyericsson.hudson.plugins.gerrit.trigger.config.IGerritHudsonTriggerConfig;
import com.sonyericsson.hudson.plugins.gerrit.trigger.config.ReplicationConfig;
import com.sonyericsson.hudson.plugins.gerrit.trigger.hudsontrigger.GerritConnectionListener;
import com.sonyericsson.hudson.plugins.gerrit.trigger.hudsontrigger.GerritTrigger;
import com.sonyericsson.hudson.plugins.gerrit.trigger.hudsontrigger.data.GerritSlave;
import com.sonyericsson.hudson.plugins.gerrit.trigger.playback.GerritMissedEventsPlaybackManager;
import com.sonyericsson.hudson.plugins.gerrit.trigger.version.GerritVersionChecker;

/**
 * Every instance of this class represents a Gerrit server having its own unique name,
 * connection, project list updater, configuration, and lists of listeners.
 * All interactions with a Gerrit server should go through this class.
 * The list of GerritServer is kept in @PluginImpl.
 *
 * @author Robert Sandell <robert.sandell@sonyericsson.com>
 * @author Mathieu Wang <mathieu.wang@ericsson.com>
 *
 */
@ExportedBean(defaultVisibility = 2)
public class GerritServer implements Describable<GerritServer>, Action {
    private static final Logger logger = LoggerFactory.getLogger(GerritServer.class);
    private static final String START_SUCCESS = "Connection started";
    private static final String START_FAILURE = "Error establising conection";
    private static final String STOP_SUCCESS = "Connection stopped";
    private static final String STOP_FAILURE = "Error terminating connection";
    /**
     * Key that is used to select to trigger a build on events from any server.
     */
    public static final String ANY_SERVER = "__ANY__";
    private static final int THREADS_FOR_TEST_CONNECTION = 1;
    private static final int TIMEOUT_FOR_TEST_CONNECTION = 10;
    private static final int RESPONSE_COUNT = 1;
    private static final int RESPONSE_INTERVAL_MS = 1000;
    private static final int RESPONSE_TIMEOUT_S = 10;
    private String name;
    @Deprecated
    private transient boolean pseudoMode;
    private boolean noConnectionOnStartup;
    private transient boolean started;
    private transient boolean timeoutWakeup = false;
    private transient String connectionResponse = "";
    private transient GerritHandler gerritEventManager;
    private transient GerritConnection gerritConnection;
    private transient GerritProjectListUpdater projectListUpdater;
    private IGerritHudsonTriggerConfig config;
    private transient GerritConnectionListener gerritConnectionListener;
    private transient GerritMissedEventsPlaybackManager missedEventsPlaybackManager;

    @Override
    public DescriptorImpl getDescriptor() {
        return Hudson.getInstance().getDescriptorByType(DescriptorImpl.class);
    }

    /**
     * Returns the Missed Events playback manager.
     * @return GerritMissedEventsPlaybackManager
     */
    public GerritMissedEventsPlaybackManager getMissedEventsPlaybackManager() {
        return missedEventsPlaybackManager;
    }

    /**
    * Convenience method for jelly to get url of the server list's page relative to root.
    *
    * @see GerritManagement#getUrlName()
    * @return the relative url
    */
    public String getParentUrl() {
        return GerritManagement.get().getUrlName();
    }

    /**
     * Convenience method for jelly to get url of this server's config page relative to root.
     *
     * @see GerritManagement#getUrlName()
     * @return the relative url
     */
    public String getUrl() {
        return GerritManagement.get().getUrlName() + "/server/" + getUrlEncodedName();
    }

    /**
     * Constructor.
     *
     * @param name the name of the server.
     */
    public GerritServer(String name) {
        this(name, false);
    }

    /**
     * Constructor.
     *
     * @param name the name of the server.
     * @param noConnectionOnStartup if noConnectionOnStartup or not.
     */
    public GerritServer(String name, boolean noConnectionOnStartup) {
        this.name = name;
        this.pseudoMode = false;
        this.noConnectionOnStartup = noConnectionOnStartup;
        config = new Config();
    }

    /**
     * Gets the global config of this server.
     *
     * @return the config.
     */
    public IGerritHudsonTriggerConfig getConfig() {
        return config;
    }

    /**
     * Sets the global config of this server.
     *
     * @param config the config.
     */
    public void setConfig(IGerritHudsonTriggerConfig config) {
        this.config = config;
    }

    /**
     * Get the name of the server.
     *
     * @return name the name of the server.
     */
    @Exported
    public String getName() {
        return name;
    }

    /**
     * Get hostname of the server.
     *
     * @return the hostname of the server.
     */
    @Exported
    public String getHostName() {
        return config.getGerritHostName();
    }

    /**
     * Get ssh port of the server.
     *
     * @return the ssh port of the server.
     */
    @Exported
    public int getSshPort() {
        return config.getGerritSshPort();
    }

    /**
     * Get username of the server.
     *
     * @return the username of the server.
     */
    @Exported
    public String getUserName() {
        return config.getGerritUserName();
    }

    /**
     * Get HTTP username of the server.
     *
     * @return HTTP username of the server.
     */
    @Exported
    public String getHttpUserName() {
        return config.getGerritHttpUserName();
    }

    /**
     * Get frontend url of the server.
     *
     * @return the frontend url of the server.
     */
    @Exported
    public String getFrontEndUrl() {
        return config.getGerritFrontEndUrl();
    }

    /**
     * If pseudo mode or not.
     *
     * @return true if so.
     */
    @Deprecated
    public boolean isPseudoMode() {
        return pseudoMode;
    }

    /**
     * Sets pseudo mode.
     *
     * @param pseudoMode true if pseudoMode connection.
     */
    @Deprecated
    public void setPseudoMode(boolean pseudoMode) {
        this.pseudoMode = pseudoMode;
    }

    /**
     * If no connection on startup or not.
     *
     * @return true if so.
     */
    @Exported
    public boolean isNoConnectionOnStartup() {
        return noConnectionOnStartup;
    }

    /**
     * Sets connect on startup.
     *
     * @param noConnectionOnStartup true if connect on startup.
     */
    public void setNoConnectionOnStartup(boolean noConnectionOnStartup) {
        this.noConnectionOnStartup = noConnectionOnStartup;
    }

    /**
     * Gets wakeup is failed by timeout or not.
     *
     * @return true if wakeup is failed by timeout.
     */
    @Exported
    public boolean isTimeoutWakeup() {
        return timeoutWakeup;
    }

    @Override
    public String getIconFileName() {
        return PLUGIN_IMAGES_URL + "icon24.png";
    }

    @Override
    public String getDisplayName() {
        return getName();
    }

    @Override
    public String getUrlName() {
        //Lets make an absolute url to circumvent some buggy things in core
        Jenkins jenkins = Jenkins.getInstance();
        if (jenkins != null && jenkins.getRootUrl() != null) {
            return Functions.joinPath(jenkins.getRootUrl(), getParentUrl(), "server", getUrlEncodedName());
        } else {
            return Functions.joinPath("/", getParentUrl(), "server", getUrlEncodedName());
        }
    }

    /**
     * Get the url encoded name of the server.
     *
     * @return the url encoded name.
     */
    public String getUrlEncodedName() {
        String urlName;
        try {
            urlName = URLEncoder.encode(name, CharEncoding.UTF_8);
        } catch (Exception ex) {
            urlName = URLEncoder.encode(name);
        }
        return urlName;
    }

    /**
     * Check whether this server is the last one.
     * Used by jelly to stop removal if true.
     *
     * @return whether it is the last one;
     */
    public boolean isLastServer() {
        return PluginImpl.getServers_().size() == 1;
    }

    /**
     * Starts the server's project list updater, send command queue and event manager.
     *
     */
    public void start() {
        logger.info("Starting GerritServer: " + name);

        //do not try to connect to gerrit unless there is a URL or a hostname in the text fields
        List<VerdictCategory> categories = config.getCategories();
        if (categories == null) {
            categories = new LinkedList<VerdictCategory>();
        }
        if (categories.isEmpty()) {
            categories.add(new VerdictCategory("Code-Review", "Code Review"));
            categories.add(new VerdictCategory("Verified", "Verified"));
        }
        config.setCategories(categories);
        gerritEventManager = PluginImpl.getHandler_();

        if (missedEventsPlaybackManager == null) {
            missedEventsPlaybackManager = new GerritMissedEventsPlaybackManager(name);
        }

        initializeConnectionListener();

        projectListUpdater = new GerritProjectListUpdater(name);
        projectListUpdater.start();

        missedEventsPlaybackManager.checkIfEventsLogPluginSupported();
        addListener((GerritEventListener) missedEventsPlaybackManager);

        logger.info(name + " started");
        started = true;
    }

    /**
     * Initializes the Gerrit connection listener for this server.
     */
    private void initializeConnectionListener() {
        gerritConnectionListener = new GerritConnectionListener(name);
        addListener(gerritConnectionListener);
        gerritConnectionListener.setConnected(isConnected());
        gerritConnectionListener.checkGerritVersionFeatures();
    }

    /**
     * Stops the server's project list updater, send command queue and event manager.
     *
     */
    public void stop() {
        logger.info("Stopping GerritServer " + name);

        if (projectListUpdater != null) {
            projectListUpdater.shutdown();
            try {
                projectListUpdater.join();
            } catch (InterruptedException ie) {
                logger.error("project list updater of " + name + "interrupted", ie);
            }
            projectListUpdater = null;
        }

        if (missedEventsPlaybackManager != null) {
            missedEventsPlaybackManager.shutdown();
            missedEventsPlaybackManager = null;
        }

        if (gerritConnection != null) {
            gerritConnection.shutdown(false);
            gerritConnection = null;
        }

        logger.info(name + " stopped");
        started = false;
    }

    /**
     * Adds a listener to the EventManager.  The listener will receive all events from Gerrit.
     *
     * @param listener the listener to add.
     * @see GerritHandler#addListener(com.sonymobile.tools.gerrit.gerritevents.GerritEventListener)
     */
    public void addListener(GerritEventListener listener) {
        if (gerritEventManager != null) {
            gerritEventManager.addListener(listener);
        }
    }

    /**
     * Removes a listener from the manager.
     *
     * @param listener the listener to remove.
     * @see GerritHandler#removeListener(com.sonymobile.tools.gerrit.gerritevents.GerritEventListener)
     */
    public void removeListener(GerritEventListener listener) {
        if (gerritEventManager != null) {
            gerritEventManager.removeListener(listener);
        }
    }

    /**
     * Removes a connection listener from the manager.
     *
     * @param listener the listener to remove.
     */
    public void removeListener(ConnectionListener listener) {
        if (gerritConnection != null) {
            gerritConnection.removeListener(listener);
        }
    }

    /**
     * Get the GerritConnectionListener for GerritAdministrativeMonitor.
     * @return the GerritConnectionListener, or null if it has not yet been initialized.
     */
    public GerritConnectionListener getGerritConnectionListener() {
        return gerritConnectionListener;
    }

    /**
     * Starts the connection to Gerrit stream of events.
     * During startup it is called by
     * {@link com.sonyericsson.hudson.plugins.gerrit.trigger.hudsontrigger.GerritItemListener}.
     *
     */
    public synchronized void startConnection() {
        if (!config.hasDefaultValues()) {
            if (gerritConnection == null) {
                logger.debug("Starting Gerrit connection...");
                gerritConnection = new GerritConnection(name, config);
                gerritEventManager.setIgnoreEMail(name, config.getGerritEMail());
                gerritConnection.setHandler(gerritEventManager);
                gerritConnection.addListener(gerritConnectionListener);
                gerritConnection.addListener(projectListUpdater);

                missedEventsPlaybackManager.checkIfEventsLogPluginSupported();
                gerritConnection.addListener(missedEventsPlaybackManager);

                gerritConnection.start();
            } else {
                logger.warn("Already started!");
            }
        }
    }

    /**
     * Stops the connection to Gerrit stream of events.
     *
     */
    public synchronized void stopConnection() {
        if (gerritConnection != null) {
            gerritConnection.shutdown(true);
            gerritConnection.removeListener(gerritConnectionListener);
            gerritConnection.removeListener(missedEventsPlaybackManager);
            gerritConnection = null;
            gerritEventManager.setIgnoreEMail(name, null);
        } else {
            logger.warn("Was told to shutdown again?");
        }
    }

    /**
     * A quick check if a connection to Gerrit is open.
     *
     * @return true if so.
     */
    @Exported
    public synchronized boolean isConnected() {
        if (gerritConnection != null) {
            return gerritConnection.isConnected();
        }
        return false;
    }

    /**
     * Restarts the connection to Gerrit stream of events.
     *
     */
    public void restartConnection() {
        stopConnection();
        startConnection();
    }

    /**
     * Adds a Connection Listener to the manager.
     * Return the current connection status so that listeners that
     * are added later than a connectionestablished/ connectiondown
     * will get the current connection status.
     *
     * @param listener the listener to be added.
     */
    public void addListener(ConnectionListener listener) {
        if (gerritConnection != null) {
            gerritConnection.addListener(listener);
        }
    }

    /**
     * Returns a list of Gerrit projects.
     *
     * @return list of gerrit projects
     */
    public List<String> getGerritProjects() {
        if (projectListUpdater != null) {
            return projectListUpdater.getGerritProjects();
        } else {
            return new ArrayList<String>();
        }
    }

    /**
     * Adds the given event to the stream of events.
     * It gets added to the same event queue as any event coming from the stream-events command in Gerrit.
     * Throws IllegalStateException if the event manager is null
     *
     * @param event the event.
     * @see GerritHandler#triggerEvent(com.sonymobile.tools.gerrit.gerritevents.dto.GerritEvent)
     */
    public void triggerEvent(GerritEvent event) {
        if (gerritEventManager != null) {
            gerritEventManager.post(event);
        } else {
            throw new IllegalStateException("Manager not started!");
        }
    }

    /**
     * Returns the current Gerrit version.
     *
     * @return the current Gerrit version as a String if connected, or null otherwise.
     */
    public String getGerritVersion() {
        if (gerritConnection != null) {
            return gerritConnection.getGerritVersion();
        } else {
            return null;
        }
    }

    /**
     * Return if the current server support replication events.
     * @return true if replication events are supported, otherwise false
     */
    public boolean isReplicationEventsSupported() {
        return GerritVersionChecker.isCorrectVersion(GerritVersionChecker.Feature.replicationEvents, name);
    }

    /**
     * Checks whether the current server support project-created events or not.
     *
     * Note: We need to exclude snapshot versions from this check. Otherwise, snapshot versions
     * that are &lt; Gerrit 2.12 will default to waiting for Project Created events which are only
     * supported in Gerrit &gt;= 2.12.
     *
     * @return true if project-created events are supported, otherwise false
     */
    public boolean isProjectCreatedEventsSupported() {
        return GerritVersionChecker.isCorrectVersion(GerritVersionChecker.Feature.projectCreatedEvents, name, true);
    }

    /**
     * Descriptor is only used for UI form bindings.
     */
    @Extension
    public static final class DescriptorImpl extends Descriptor<GerritServer> {

        @Override
        public String getDisplayName() {
            return "Gerrit Server with Default Configurations";
        }

        /**
         * Tests if the provided parameters can connect to Gerrit.
         * @param gerritHostName the hostname
         * @param gerritSshPort the ssh-port
         * @param gerritProxy the proxy url
         * @param gerritUserName the username
         * @param gerritAuthKeyFile the private key file
         * @param gerritAuthKeyFilePassword the password for the keyfile or null if there is none.
         * @return {@link FormValidation#ok() } if can be done,
         *         {@link FormValidation#error(java.lang.String) } otherwise.
         */
        public FormValidation doTestConnection(@QueryParameter("gerritHostName") final String gerritHostName,
                @QueryParameter("gerritSshPort") final int gerritSshPort,
                @QueryParameter("gerritProxy") final String gerritProxy,
                @QueryParameter("gerritUserName") final String gerritUserName,
                @QueryParameter("gerritAuthKeyFile") final String gerritAuthKeyFile,
                @QueryParameter("gerritAuthKeyFilePassword") final String gerritAuthKeyFilePassword) {
            if (logger.isDebugEnabled()) {
                logger.debug("gerritHostName = {}\n" + "gerritSshPort = {}\n" + "gerritProxy = {}\n"
                        + "gerritUserName = {}\n" + "gerritAuthKeyFile = {}\n" + "gerritAuthKeyFilePassword = {}",
                        new Object[] { gerritHostName, gerritSshPort, gerritProxy, gerritUserName,
                                gerritAuthKeyFile, gerritAuthKeyFilePassword, });
            }

            File file = new File(gerritAuthKeyFile);
            String password = null;
            if (gerritAuthKeyFilePassword != null && gerritAuthKeyFilePassword.length() > 0) {
                password = Secret.fromString(gerritAuthKeyFilePassword).getPlainText();
            }
            if (SshUtil.checkPassPhrase(file, password)) {
                if (file.exists() && file.isFile()) {
                    try {
                        final SshConnection sshConnection = SshConnectionFactory.getConnection(gerritHostName,
                                gerritSshPort, gerritProxy, new Authentication(file, gerritUserName, password));
                        ExecutorService service = Executors.newFixedThreadPool(THREADS_FOR_TEST_CONNECTION);
                        Future<Integer> future = service.submit(new Callable<Integer>() {
                            @Override
                            public Integer call() throws Exception {
                                return sshConnection.executeCommandReader(GerritConnection.CMD_STREAM_EVENTS)
                                        .read();
                            }
                        });
                        int readChar;
                        try {
                            readChar = future.get(TIMEOUT_FOR_TEST_CONNECTION, TimeUnit.SECONDS);
                        } catch (TimeoutException ex) {
                            readChar = 0;
                        } finally {
                            sshConnection.disconnect();
                        }
                        if (readChar < 0) {
                            return FormValidation.error(Messages.StreamEventsCapabilityException(gerritUserName));
                        } else {
                            return FormValidation.ok(Messages.Success());
                        }
                    } catch (SshConnectException ex) {
                        return FormValidation.error(Messages.SshConnectException());
                    } catch (SshAuthenticationException ex) {
                        return FormValidation.error(Messages.SshAuthenticationException(ex.getMessage()));
                    } catch (Exception e) {
                        return FormValidation.error(Messages.ConnectionError(e.getMessage()));
                    }
                } else {
                    return FormValidation.error(Messages.SshKeyFileNotFoundError(gerritAuthKeyFile));
                }
            } else {
                return FormValidation.error(Messages.BadSshkeyOrPasswordError());
            }

        }

        /**
         * Tests if the REST API settings can connect to Gerrit.
         *
         * @param gerritFrontEndUrl the url
         * @param gerritHttpUserName the user name
         * @param gerritHttpPassword the password
         * @return {@link FormValidation#ok()} if it works.
         */
        public FormValidation doTestRestConnection(
                @QueryParameter("gerritFrontEndUrl") final String gerritFrontEndUrl,
                @QueryParameter("gerritHttpUserName") final String gerritHttpUserName,
                @QueryParameter("gerritHttpPassword") final String gerritHttpPassword) {

            String password = Secret.fromString(gerritHttpPassword).getPlainText();

            String restUrl = gerritFrontEndUrl;
            if (gerritFrontEndUrl != null && !gerritFrontEndUrl.endsWith("/")) {
                restUrl = gerritFrontEndUrl + "/";
            }
            CredentialsProvider credsProvider = new BasicCredentialsProvider();
            credsProvider.setCredentials(new AuthScope(null, -1),
                    new UsernamePasswordCredentials(gerritHttpUserName, password));
            HttpClient httpclient = HttpClients.custom().setDefaultCredentialsProvider(credsProvider).build();
            HttpGet httpGet = new HttpGet(restUrl + "a/projects/?d");
            HttpResponse execute;
            try {
                execute = httpclient.execute(httpGet);
            } catch (IOException e) {
                return FormValidation.error(Messages.ConnectionError(e.getMessage()));
            }

            int statusCode = execute.getStatusLine().getStatusCode();
            switch (statusCode) {
            case HttpURLConnection.HTTP_OK:
                return FormValidation.ok(Messages.Success());
            case HttpURLConnection.HTTP_UNAUTHORIZED:
                return FormValidation.error(Messages.HttpConnectionUnauthorized());
            default:
                return FormValidation.error(Messages.HttpConnectionError(statusCode));
            }

        }

        /**
         * Fill the Gerrit slave dropdown with the list of slaves configured with the selected server.
         * Expected to be called only when slave config is enabled at job level.
         *
         * @param serverName name of the server
         * @return list of slaves.
         */
        public ListBoxModel doFillDefaultSlaveIdItems(
                @QueryParameter("name") @RelativePath("../..") final String serverName) {
            ListBoxModel items = new ListBoxModel();
            logger.trace("filling default gerrit slave drop down for sever {}", serverName);
            GerritServer server = PluginImpl.getServer_(serverName);
            if (server == null) {
                logger.warn(Messages.CouldNotFindServer(serverName));
                items.add(Messages.CouldNotFindServer(serverName), "");
                return items;
            }
            ReplicationConfig replicationConfig = server.getConfig().getReplicationConfig();
            if (replicationConfig == null || !replicationConfig.isEnableReplication()
                    || replicationConfig.getGerritSlaves().size() == 0) {
                logger.trace(Messages.GerritSlaveNotDefined());
                items.add(Messages.GerritSlaveNotDefined(), "");
                return items;
            }
            for (GerritSlave slave : replicationConfig.getGerritSlaves()) {
                boolean selected;
                if (slave.getId().equals(replicationConfig.getDefaultSlaveId())) {
                    selected = true;
                } else {
                    selected = false;
                }
                items.add(new ListBoxModel.Option(slave.getName(), slave.getId(), selected));
            }
            return items;
        }

        /**
         * Fill the dropdown for notification levels.
         *
         * @return the values.
         */
        public ListBoxModel doFillNotificationLevelItems() {
            Map<Notify, String> levelTextsById = notificationLevelTextsById();
            ListBoxModel items = new ListBoxModel(levelTextsById.size());
            for (Entry<Notify, String> level : levelTextsById.entrySet()) {
                items.add(new Option(level.getValue(), level.getKey().toString()));
            }
            return items;
        }
    }

    /**
     * Returns localized texts for each known notification value.
     *
     * @return a map with level id to level text.
     */
    public static Map<Notify, String> notificationLevelTextsById() {
        ResourceBundleHolder holder = ResourceBundleHolder.get(Messages.class);
        Map<Notify, String> textsById = new LinkedHashMap<Notify, String>(Notify.values().length, 1);
        for (Notify level : Notify.values()) {
            textsById.put(level, holder.format("NotificationLevel_" + level));
        }
        return textsById;
    }

    /**
      * Saves the form to the configuration and disk.
      * @param req StaplerRequest
      * @param rsp StaplerResponse
      * @throws ServletException if something unfortunate happens.
      * @throws IOException if something unfortunate happens.
      * @throws InterruptedException if something unfortunate happens.
      */
    public void doConfigSubmit(StaplerRequest req, StaplerResponse rsp)
            throws ServletException, IOException, InterruptedException {
        if (logger.isDebugEnabled()) {
            logger.debug("submit {}", req.toString());
        }
        JSONObject form = req.getSubmittedForm();

        String newName = form.getString("name");
        if (!name.equals(newName)) {
            if (PluginImpl.containsServer_(newName)) {
                throw new Failure("A server already exists with the name '" + newName + "'");
            } else if (ANY_SERVER.equals(newName)) {
                throw new Failure("Illegal name '" + newName + "'");
            }
            rename(newName);
        }
        noConnectionOnStartup = form.getBoolean("noConnectionOnStartup");
        config.setValues(form);

        PluginImpl.save_();

        if (!started) {
            this.start();
        } else {
            if (missedEventsPlaybackManager != null) {
                missedEventsPlaybackManager.checkIfEventsLogPluginSupported();
            }
        }

        rsp.sendRedirect("../..");
    }

    /**
     * Rename the server.
     * Assumes that newName is different from current name.
     *
     * @param newName the new name
     */
    private void rename(String newName) {
        if (isConnected()) {
            stopConnection();
            stop();
            String oldName = name;
            name = newName;
            start();
            startConnection();
            changeSelectedServerInJobs(oldName);
        } else {
            stop();
            String oldName = name;
            name = newName;
            start();
            changeSelectedServerInJobs(oldName);
        }
    }

    /**
     * Convenience method for remove.jelly.
     *
     * @return the list of jobs configured with this server.
     */
    public List<Job> getConfiguredJobs() {
        return PluginImpl.getConfiguredJobs_(name);
    }

    /**
     * Change the selectedServer value in jobs to select the new name.
     *
     * @param oldName the old name of the Gerrit server
     */
    private void changeSelectedServerInJobs(String oldName) {
        for (Job job : PluginImpl.getConfiguredJobs_(oldName)) {

            if (!(job instanceof AbstractProject)) {
                logger.warn("Unable to modify Gerrit Trigger configurations for job [" + job.getName()
                        + "] after Gerrit server has been renamed from [" + oldName + "] to [" + name + "]."
                        + " This feature is only supported for AbstractProject types e.g. Freestyle Jobs.");
                return;
            }
            AbstractProject project = (AbstractProject) job;

            GerritTrigger trigger = (GerritTrigger) project.getTrigger(GerritTrigger.class);
            if (trigger != null) {
                try {
                    trigger.setServerName(name);
                    trigger.start(job, false);
                    project.addTrigger(trigger);
                    project.save();
                } catch (IOException e) {
                    logger.error("Error saving Gerrit Trigger configurations for job [" + job.getName()
                            + "] after Gerrit server has been renamed from [" + oldName + "] to [" + name + "]");
                }
            }
        }
    }

    /**
     * Remove "Gerrit event" as a trigger in all jobs selecting this server.
     */
    private void removeGerritTriggerInJobs() {
        for (Job job : getConfiguredJobs()) {

            if (!(job instanceof AbstractProject)) {
                logger.warn("Unable to remove Gerrit Trigger ffrom job [" + job.getName() + "]. "
                        + " This feature is only supported for AbstractProject types e.g. Freestyle Jobs.");
                return;
            }
            AbstractProject project = (AbstractProject) job;

            GerritTrigger trigger = (GerritTrigger) project.getTrigger(GerritTrigger.class);
            trigger.stop();
            try {
                project.removeTrigger(trigger.getDescriptor());
            } catch (IOException e) {
                logger.error(
                        "Error removing Gerrit trigger from job [" + job.getName() + "]. Please check job config");
            }
            trigger = null;
            try {
                job.save();
            } catch (IOException e) {
                logger.error("Error saving configuration of job [" + job.getName()
                        + "] while trying to remove Gerrit server [" + name + "]. Please check job config.");
            }
        }
    }

    /**
     * Wakeup server. This method returns after actual connection status is changed or timeout.
     * Used by jelly.
     *
     * @return connection status.
     */
    public JSONObject doWakeup() {
        Timer timer = new Timer();
        try {
            startConnection();

            final CountDownLatch responseLatch = new CountDownLatch(RESPONSE_COUNT);
            timer.schedule(new TimerTask() {
                @Override
                public void run() {
                    if (gerritConnectionListener != null && gerritConnectionListener.isConnected()) {
                        responseLatch.countDown();
                    }
                }
            }, RESPONSE_INTERVAL_MS, RESPONSE_INTERVAL_MS);

            if (responseLatch.await(RESPONSE_TIMEOUT_S, TimeUnit.SECONDS)) {
                timeoutWakeup = false;
                setConnectionResponse(START_SUCCESS);
            } else {
                timeoutWakeup = true;
                throw new InterruptedException("time out.");
            }
        } catch (Exception ex) {
            setConnectionResponse(START_FAILURE);
            logger.error("Could not start connection. ", ex);
        }
        timer.cancel();

        JSONObject obj = new JSONObject();
        String status = "down";
        if (gerritConnectionListener != null) {
            if (gerritConnectionListener.isConnected()) {
                status = "up";
            }
        }
        obj.put("status", status);
        return obj;
    }

    /**
     * Server to sleep. This method returns actual connection status is changed or timeout.
     * Used by jelly.
     *
     * @return connection status.
     */
    public JSONObject doSleep() {
        Timer timer = new Timer();
        try {
            stopConnection();

            final CountDownLatch responseLatch = new CountDownLatch(RESPONSE_COUNT);
            timer.schedule(new TimerTask() {
                @Override
                public void run() {
                    if (gerritConnectionListener == null || !gerritConnectionListener.isConnected()) {
                        responseLatch.countDown();
                    }
                }
            }, RESPONSE_INTERVAL_MS, RESPONSE_INTERVAL_MS);

            if (responseLatch.await(RESPONSE_TIMEOUT_S, TimeUnit.SECONDS)) {
                setConnectionResponse(STOP_SUCCESS);
            } else {
                throw new InterruptedException("time out.");
            }
        } catch (Exception ex) {
            setConnectionResponse(STOP_FAILURE);
            logger.error("Could not stop connection. ", ex);
        }
        timer.cancel();

        JSONObject obj = new JSONObject();
        String status = "down";
        if (gerritConnectionListener != null) {
            if (gerritConnectionListener.isConnected()) {
                status = "up";
            }
        }
        obj.put("status", status);
        return obj;
    }

    /**
     * This server has errors or not.
     *
     * @return true if this server has errors.
     */
    public boolean hasErrors() {
        if (isConnectionError()) {
            return true;
        }
        return false;
    }

    /**
     * This server has warnings or not.
     *
     * @return true if this server has warnings.
     */
    public boolean hasWarnings() {
        if (isGerritSnapshotVersion() || hasDisabledFeatures()) {
            return true;
        }
        return false;
    }

    /**
     * If connection could not be established.
     *
     * @return true if so. false otherwise.
     */
    @JavaScriptMethod
    public boolean isConnectionError() {
        //if it is null then we haven't started at all.
        if (gerritConnectionListener != null && !gerritConnectionListener.isConnected()) {
            if (timeoutWakeup) {
                return true;
            }
        }
        return false;
    }

    /**
     * If Gerrit is a snapshot version.
     *
     * @return true if so, false otherwise.
     */
    @JavaScriptMethod
    public boolean isGerritSnapshotVersion() {
        if (gerritConnectionListener != null && gerritConnectionListener.isConnected()) {
            if (gerritConnectionListener.isSnapShotGerrit()) {
                return true;
            }
        }
        return false;
    }

    /**
     * If Gerrit Missed Events Playback is supported.
     *
     * @return true if so, false otherwise.
     */
    @JavaScriptMethod
    public boolean isGerritMissedEventsSupported() {
        if (gerritConnectionListener != null && gerritConnectionListener.isConnected()) {
            return missedEventsPlaybackManager.isSupported();
        }
        return false;
    }

    /**
     * If server with features disabled due to old Gerrit version.
     *
     * @return true if so, false otherwise.
     */
    @JavaScriptMethod
    public boolean hasDisabledFeatures() {
        if (gerritConnectionListener != null && gerritConnectionListener.isConnected()) {
            List<GerritVersionChecker.Feature> disabledFeatures = gerritConnectionListener.getDisabledFeatures();
            if (disabledFeatures != null && !disabledFeatures.isEmpty()) {
                return true;
            }
        }
        return false;
    }

    /**
     * Returns the list of disabled features.
     *
     * @return the list of disabled features or empty list if listener not found
     */
    public List<GerritVersionChecker.Feature> getDisabledFeatures() {
        if (gerritConnectionListener != null && gerritConnectionListener.isConnected()) {
            List<GerritVersionChecker.Feature> features = gerritConnectionListener.getDisabledFeatures();
            if (features != null) {
                return features;
            }
        }
        return new LinkedList<GerritVersionChecker.Feature>();
    }

    /**
     * Get the response after a start/stop/restartConnection; Used by jelly.
     * @return the connection response
     */
    public String getConnectionResponse() {
        return connectionResponse;
    }

    /**
     * Set the connection status.
     * @param response the response to be set.
     */
    private void setConnectionResponse(String response) {
        connectionResponse = response;
    }

    /**
     * Saves the form to the configuration and disk.
     * @param req StaplerRequest
     * @param rsp StaplerResponse
     * @throws ServletException if something unfortunate happens.
     * @throws IOException if something unfortunate happens.
     * @throws InterruptedException if something unfortunate happens.
     */
    public void doRemoveConfirm(StaplerRequest req, StaplerResponse rsp)
            throws ServletException, IOException, InterruptedException {

        stopConnection();
        stop();
        PluginImpl plugin = PluginImpl.getInstance();
        removeGerritTriggerInJobs();
        if (plugin != null) {
            plugin.removeServer(this);
            plugin.save();
        }

        Jenkins jenkins = Jenkins.getInstance();
        if (jenkins != null) {
            rsp.sendRedirect(jenkins.getRootUrl() + GerritManagement.get().getUrlName());
        }
    }

    /**
     * Checks that the provided parameter is an integer and not negative.
     * @param value the value.
     * @return {@link FormValidation#validatePositiveInteger(String)}
     */
    public FormValidation doPositiveIntegerCheck(@QueryParameter("value") final String value) {

        return FormValidation.validatePositiveInteger(value);
    }

    /**
     * Checks that the provided parameter is an integer and not negative, zero is accepted.
     *
     * @param value the value.
     * @return {@link FormValidation#validateNonNegativeInteger(String)}
     */
    public FormValidation doNonNegativeIntegerCheck(@QueryParameter("value") final String value) {

        return FormValidation.validateNonNegativeInteger(value);
    }

    /**
     * Checks that the provided parameter is an integer, not negative, that is larger
     * than the minimum value.
     * @param value the value.
     * @return {@link FormValidation#validatePositiveInteger(String)}
     */
    public FormValidation doDynamicConfigRefreshCheck(@QueryParameter("value") final String value) {

        FormValidation validatePositive = FormValidation.validatePositiveInteger(value);
        if (!validatePositive.kind.equals(FormValidation.Kind.OK)) {
            return validatePositive;
        } else {
            int intValue = Integer.parseInt(value);
            if (intValue < GerritDefaultValues.MINIMUM_DYNAMIC_CONFIG_REFRESH_INTERVAL) {
                return FormValidation.error(Messages.DynamicConfRefreshTooLowError(
                        GerritDefaultValues.MINIMUM_DYNAMIC_CONFIG_REFRESH_INTERVAL));
            }
        }
        return FormValidation.ok();
    }

    /**
     * Checks that the provided parameter is an integer.
     * @param value the value.
     * @return {@link FormValidation#validatePositiveInteger(String)}
     */
    public FormValidation doIntegerCheck(@QueryParameter("value") final String value) {

        try {
            Integer.parseInt(value);
            return FormValidation.ok();
        } catch (NumberFormatException e) {
            return FormValidation.error(hudson.model.Messages.Hudson_NotANumber());
        }
    }

    /**
     * Checks that the provided parameter is an empty string or an integer.
     * @param value the value.
     * @return {@link FormValidation#validatePositiveInteger(String)}
     */
    public FormValidation doEmptyOrIntegerCheck(@QueryParameter("value") final String value) {

        if (value == null || value.length() <= 0) {
            return FormValidation.ok();
        } else {
            try {
                Integer.parseInt(value);
                return FormValidation.ok();
            } catch (NumberFormatException e) {
                return FormValidation.error(hudson.model.Messages.Hudson_NotANumber());
            }
        }
    }

    /**
     * Checks if the value is a valid URL. It does not check if the URL is reachable.
     * @param value the value
     * @return {@link FormValidation#ok() } if it is so.
     */
    public FormValidation doUrlCheck(@QueryParameter("value") final String value) {

        if (value == null || value.length() <= 0) {
            return FormValidation.error(Messages.EmptyError());
        } else {
            try {
                new URL(value);

                return FormValidation.ok();
            } catch (MalformedURLException ex) {
                return FormValidation.error(Messages.BadUrlError());
            }
        }
    }

    /**
     * Checks to see if the provided value is a file path to a valid private key file.
     * @param value the value.
     * @return {@link FormValidation#ok() } if it is so.
     */
    public FormValidation doValidKeyFileCheck(@QueryParameter("value") final String value) {

        File f = new File(value);
        if (!f.exists()) {
            return FormValidation.error(Messages.FileNotFoundError(value));
        } else if (!f.isFile()) {
            return FormValidation.error(Messages.NotFileError(value));
        } else {
            if (SshUtil.isPrivateKeyFileValid(f)) {
                return FormValidation.ok();
            } else {
                return FormValidation.error(Messages.InvalidKeyFileError(value));
            }
        }
    }

    /**
     * Checks to see if the provided value represents a time on the hh:mm format.
     * Also checks that from is before to.
     *
     * @param fromValue the from value.
     * @param toValue the to value.
     * @return {@link FormValidation#ok() } if it is so.
     */
    public FormValidation doValidTimeCheck(@QueryParameter final String fromValue,
            @QueryParameter final String toValue) {
        String[] splitFrom = fromValue.split(":");
        String[] splitTo = toValue.split(":");
        int fromHour;
        int fromMinute;
        int toHour;
        int toMinute;
        if (splitFrom.length != 2 || splitTo.length != 2) {
            return FormValidation.error(Messages.InvalidTimeString());
        }
        try {
            fromHour = Integer.parseInt(splitFrom[0]);
            fromMinute = Integer.parseInt(splitFrom[1]);
            toHour = Integer.parseInt(splitTo[0]);
            toMinute = Integer.parseInt(splitTo[1]);

        } catch (NumberFormatException nfe) {
            return FormValidation.error(Messages.InvalidTimeString());
        }

        if (fromHour < MIN_HOUR || fromHour > MAX_HOUR || fromMinute < MIN_MINUTE || fromMinute > MAX_MINUTE
                || toHour < MIN_HOUR || toHour > MAX_HOUR || toMinute < MIN_MINUTE || toMinute > MAX_MINUTE) {
            return FormValidation.error(Messages.InvalidTimeString());
        }
        if (fromHour > toHour || (fromHour == toHour && fromMinute > toMinute)) {
            return FormValidation.error(Messages.InvalidTimeSpan());
        }
        return FormValidation.ok();
    }

    /**
     * Checks whether server name already exists.
     * @param value the value of the name field.
     * @return ok or error.
     */
    public FormValidation doNameFreeCheck(@QueryParameter("value") final String value) {
        if (!value.equals(name)) {
            if (PluginImpl.containsServer_(value)) {
                return FormValidation.error("The server name " + value + " is already in use!");
            } else if (ANY_SERVER.equals(value)) {
                return FormValidation.error("Illegal name " + value + "!");
            } else {
                return FormValidation.warning("The server " + name + " will be renamed");
            }
        } else {
            return FormValidation.ok();
        }
    }

    /**
     * Generates a list of helper objects for the jelly view.
     *
     * @return a list of helper objects.
     */
    public List<ExceptionDataHelper> generateHelper() {
        WatchTimeExceptionData data = config.getExceptionData();
        List<ExceptionDataHelper> list = new LinkedList<ExceptionDataHelper>();
        list.add(new ExceptionDataHelper(Messages.MondayDisplayName(), Calendar.MONDAY, data));
        list.add(new ExceptionDataHelper(Messages.TuesdayDisplayName(), Calendar.TUESDAY, data));
        list.add(new ExceptionDataHelper(Messages.WednesdayDisplayName(), Calendar.WEDNESDAY, data));
        list.add(new ExceptionDataHelper(Messages.ThursdayDisplayName(), Calendar.THURSDAY, data));
        list.add(new ExceptionDataHelper(Messages.FridayDisplayName(), Calendar.FRIDAY, data));
        list.add(new ExceptionDataHelper(Messages.SaturdayDisplayName(), Calendar.SATURDAY, data));
        list.add(new ExceptionDataHelper(Messages.SundayDisplayName(), Calendar.SUNDAY, data));
        return list;
    }
}