/*
* The MIT License
*
* Copyright 2010 Sony Ericsson Mobile Communications. All rights reserved.
* Copyright 2012 Sony Mobile Communications AB. 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.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.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.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;
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.UnreviewedPatchesListener;
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.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";
private static final String RESTART_SUCCESS = "Connection restarted";
private static final String RESTART_FAILURE = "Error restarting 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 transient UnreviewedPatchesListener unreviewedPatchesListener;
private IGerritHudsonTriggerConfig config;
private transient GerritConnectionListener gerritConnectionListener;
@Override
public DescriptorImpl getDescriptor() {
return Hudson.getInstance().getDescriptorByType(DescriptorImpl.class);
}
/**
* Convenience method for jelly to get url of the server list's page relative to root.
* @link {@link 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.
* @link {@link 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
if (Jenkins.getInstance().getRootUrl() != null) {
return Functions.joinPath(Jenkins.getInstance().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.getInstance().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("CRVW", "Code Review"));
categories.add(new VerdictCategory("VRIF", "Verified"));
}
config.setCategories(categories);
gerritEventManager = PluginImpl.getInstance().getHandler();
initializeConnectionListener();
projectListUpdater = new GerritProjectListUpdater(name);
projectListUpdater.start();
//Starts unreviewed patches listener
unreviewedPatchesListener = new UnreviewedPatchesListener(name);
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 (unreviewedPatchesListener != null) {
unreviewedPatchesListener.shutdown();
unreviewedPatchesListener = 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);
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 = 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);
}
/**
* 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 = gerritAuthKeyFilePassword;
}
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 restUrl = gerritFrontEndUrl;
if (gerritFrontEndUrl != null && !gerritFrontEndUrl.endsWith("/")) {
restUrl = gerritFrontEndUrl + "/";
}
DefaultHttpClient httpclient = new DefaultHttpClient();
HttpGet httpGet = new HttpGet(restUrl + "a/projects/?d");
httpclient.getCredentialsProvider().setCredentials(new AuthScope(null, -1),
new UsernamePasswordCredentials(gerritHttpUserName,
gerritHttpPassword));
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.getInstance().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");
boolean renamed = false;
if (!name.equals(newName)) {
if (PluginImpl.getInstance().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);
renamed = true;
}
noConnectionOnStartup = form.getBoolean("noConnectionOnStartup");
config.setValues(form);
PluginImpl.getInstance().save();
if (!started) {
this.start();
}
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<AbstractProject> getConfiguredJobs() {
return PluginImpl.getInstance().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 (AbstractProject job : PluginImpl.getInstance().getConfiguredJobs(oldName)) {
GerritTrigger trigger = (GerritTrigger)job.getTrigger(GerritTrigger.class);
try {
trigger.setServerName(name);
trigger.start(job, false);
job.addTrigger(trigger);
job.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 (AbstractProject job : getConfiguredJobs()) {
GerritTrigger trigger = (GerritTrigger)job.getTrigger(GerritTrigger.class);
trigger.stop();
try {
job.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 (!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.isConnected()) {
if (gerritConnectionListener.isSnapShotGerrit()) {
return true;
}
}
return false;
}
/**
* If server with features disabled due to old Gerrit version.
*
* @return true if so, false otherwise.
*/
@JavaScriptMethod
public boolean hasDisabledFeatures() {
if (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.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();
plugin.removeServer(this);
plugin.save();
rsp.sendRedirect(Jenkins.getInstance().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.getInstance().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;
}
}