Skip to content
Permalink
Browse files
[JENKINS-31113] - Configurable HTTP timeout parameter for JiraRestSer…
…vice
  • Loading branch information
jan-zajic committed Jan 12, 2016
1 parent dc687a1 commit 06ec4d96dbe359c1a5016f247609521693568d05
@@ -42,14 +42,13 @@
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;

public class JiraRestService {

private static final Logger LOGGER = Logger.getLogger(JiraRestService.class.getName());

public static final int TIMEOUT_IN_10_SECONDS = 10000;

public static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormat.forPattern("yyyy-MM-dd");

/**
@@ -67,10 +66,18 @@ public class JiraRestService {
private final String authHeader;

private final String baseApiPath;

private final int timeout;

@Deprecated
public JiraRestService(URI uri, JiraRestClient jiraRestClient, String username, String password) {
this(uri, jiraRestClient, username, password, JiraSite.DEFAULT_TIMEOUT);
}

public JiraRestService(URI uri, JiraRestClient jiraRestClient, String username, String password, int timeout) {
this.uri = uri;
this.objectMapper = new ObjectMapper();
this.timeout = timeout;
final String login = username + ":" + password;
try {
byte[] encodeBase64 = Base64.encodeBase64(login.getBytes("UTF-8"));
@@ -109,15 +116,15 @@ public void addComment(String issueId, String commentBody,
}

try {
jiraRestClient.getIssueClient().addComment(builder.build(), comment).get(10, TimeUnit.SECONDS);
jiraRestClient.getIssueClient().addComment(builder.build(), comment).get(timeout, TimeUnit.SECONDS);
} catch (Exception e) {
LOGGER.warning("jira rest client add comment error. cause: " + e.getMessage());
}
}

public Issue getIssue(String issueKey) {
try {
return jiraRestClient.getIssueClient().getIssue(issueKey).get(10, TimeUnit.SECONDS);
return jiraRestClient.getIssueClient().getIssue(issueKey).get(timeout, TimeUnit.SECONDS);
} catch (Exception e) {
LOGGER.warning("jira rest client get issue error. cause: " + e.getMessage());
return null;
@@ -126,7 +133,7 @@ public Issue getIssue(String issueKey) {

public List<IssueType> getIssueTypes() {
try {
return Lists.newArrayList(jiraRestClient.getMetadataClient().getIssueTypes().get(10, TimeUnit.SECONDS));
return Lists.newArrayList(jiraRestClient.getMetadataClient().getIssueTypes().get(timeout, TimeUnit.SECONDS));
} catch (Exception e) {
LOGGER.warning("jira rest client get issue types error. cause: " + e.getMessage());
return Collections.emptyList();
@@ -136,7 +143,7 @@ public List<IssueType> getIssueTypes() {
public List<String> getProjectsKeys() {
Iterable<BasicProject> projects = Collections.emptyList();
try {
projects = jiraRestClient.getProjectClient().getAllProjects().get(10, TimeUnit.SECONDS);
projects = jiraRestClient.getProjectClient().getAllProjects().get(timeout, TimeUnit.SECONDS);
} catch (Exception e) {
LOGGER.warning("jira rest client get project keys error. cause: " + e.getMessage());
}
@@ -151,7 +158,7 @@ public List<Issue> getIssuesFromJqlSearch(String jqlSearch, Integer maxResults)
try {
final SearchResult searchResult = jiraRestClient.getSearchClient()
.searchJql(jqlSearch, maxResults, 0, null)
.get(10, TimeUnit.SECONDS);
.get(timeout, TimeUnit.SECONDS);
return Lists.newArrayList(searchResult.getIssues());
} catch (Exception e) {
LOGGER.warning("jira rest client get issue from jql search error. cause: " + e.getMessage());
@@ -192,7 +199,7 @@ public Version addVersion(String projectKey, String versionName) {
final VersionInput versionInput = new VersionInput(projectKey, versionName, null, null, false, false);
try {
return jiraRestClient.getVersionRestClient()
.createVersion(versionInput).get(10, TimeUnit.SECONDS);
.createVersion(versionInput).get(timeout, TimeUnit.SECONDS);
} catch (Exception e) {
LOGGER.warning("jira rest client add version error. cause: " + e.getMessage());
return null;
@@ -207,7 +214,7 @@ public void releaseVersion(String projectKey, Version version) {
.getReleaseDate(), version.isArchived(), version.isReleased());

try {
jiraRestClient.getVersionRestClient().updateVersion(builder.build(), versionInput).get(10, TimeUnit.SECONDS);
jiraRestClient.getVersionRestClient().updateVersion(builder.build(), versionInput).get(timeout, TimeUnit.SECONDS);
}catch (Exception e) {
LOGGER.warning("jira rest client release version error. cause: " + e.getMessage());
}
@@ -229,7 +236,7 @@ public BasicIssue createIssue(String projectKey, String description, String assi
final IssueInput issueInput = builder.build();

try {
return jiraRestClient.getIssueClient().createIssue(issueInput).get(10, TimeUnit.SECONDS);
return jiraRestClient.getIssueClient().createIssue(issueInput).get(timeout, TimeUnit.SECONDS);
} catch (Exception e) {
LOGGER.warning("JIRA REST createIssue error: " + e.getMessage());
return null;
@@ -238,30 +245,41 @@ public BasicIssue createIssue(String projectKey, String description, String assi

public User getUser(String username) {
try {
return jiraRestClient.getUserClient().getUser(username).get(10, TimeUnit.SECONDS);
return jiraRestClient.getUserClient().getUser(username).get(timeout, TimeUnit.SECONDS);
} catch (Exception e) {
LOGGER.warning("jira rest client get user error. cause: " + e.getMessage());
return null;
}
}

public void updateIssue(String issueKey, List<Version> fixVersions) {
final IssueInput issueInput = new IssueInputBuilder().setFixVersions(fixVersions)
.build();
try {
jiraRestClient.getIssueClient().updateIssue(issueKey, issueInput).get(10, TimeUnit.SECONDS);
jiraRestClient.getIssueClient().updateIssue(issueKey, issueInput).get(timeout, TimeUnit.SECONDS);
} catch (Exception e) {
LOGGER.warning("jira rest client update issue error. cause: " + e.getMessage());
}
}


public void setIssueLabels(String issueKey, List<String> labels) {
final IssueInput issueInput = new IssueInputBuilder()
.setFieldValue(IssueFieldId.LABELS_FIELD.id, labels)
.build();
try {
jiraRestClient.getIssueClient().updateIssue(issueKey, issueInput).get(timeout, TimeUnit.SECONDS);
} catch (Exception e) {
LOGGER.log(Level.WARNING, "jira rest client update labels error for issue "+issueKey, e);
}
}

public Issue progressWorkflowAction(String issueKey, Integer actionId) {
final TransitionInput transitionInput = new TransitionInput(actionId);

final Issue issue = getIssue(issueKey);

try {
jiraRestClient.getIssueClient().transition(issue, transitionInput).get(10, TimeUnit.SECONDS);
jiraRestClient.getIssueClient().transition(issue, transitionInput).get(timeout, TimeUnit.SECONDS);
} catch (Exception e) {
LOGGER.warning("jira rest client process workflow action error. cause: " + e.getMessage());
}
@@ -274,7 +292,7 @@ public List<Transition> getAvailableActions(String issueKey) {
try {
final Iterable<Transition> transitions = jiraRestClient.getIssueClient()
.getTransitions(issue)
.get(10, TimeUnit.SECONDS);
.get(timeout, TimeUnit.SECONDS);
return Lists.newArrayList(transitions);
} catch (Exception e) {
LOGGER.warning("jira rest client get available actions error. cause: " + e.getMessage());
@@ -285,7 +303,7 @@ public List<Transition> getAvailableActions(String issueKey) {
public List<Status> getStatuses() {
try {
final Iterable<Status> statuses = jiraRestClient.getMetadataClient().getStatuses()
.get(10, TimeUnit.SECONDS);
.get(timeout, TimeUnit.SECONDS);
return Lists.newArrayList(statuses);
} catch (Exception e) {
LOGGER.warning("jira rest client get statuses error. cause: " + e.getMessage());
@@ -329,12 +347,16 @@ public List<Component> getComponents(String projectKey) {

private Request buildGetRequest(URI uri) {
return Request.Get(uri)
.connectTimeout(TIMEOUT_IN_10_SECONDS)
.socketTimeout(TIMEOUT_IN_10_SECONDS)
.connectTimeout(timeoutInMiliseconds())
.socketTimeout(timeoutInMiliseconds())
.addHeader("Authorization", authHeader)
.addHeader("Content-Type", "application/json");
}

protected int timeoutInMiliseconds() {
return (int) TimeUnit.SECONDS.toMillis(timeout);
}

public String getBaseApiPath() {
return baseApiPath;
}
@@ -1,26 +1,8 @@
package hudson.plugins.jira;

import com.atlassian.jira.rest.client.api.JiraRestClient;
import com.atlassian.jira.rest.client.api.RestClientException;
import com.atlassian.jira.rest.client.api.domain.Issue;
import com.atlassian.jira.rest.client.api.domain.IssueType;
import com.atlassian.jira.rest.client.api.domain.Version;
import com.atlassian.jira.rest.client.internal.async.AsynchronousJiraRestClientFactory;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import hudson.Extension;
import hudson.Util;
import hudson.model.AbstractDescribableImpl;
import hudson.model.AbstractProject;
import hudson.model.Descriptor;
import hudson.util.FormValidation;
import hudson.util.Secret;
import org.joda.time.DateTime;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.QueryParameter;
import static org.apache.commons.lang.StringUtils.isEmpty;
import static org.apache.commons.lang.StringUtils.isNotEmpty;

import javax.annotation.CheckForNull;
import javax.annotation.Nullable;
import java.io.IOException;
import java.io.PrintStream;
import java.net.MalformedURLException;
@@ -42,8 +24,30 @@
import java.util.logging.Logger;
import java.util.regex.Pattern;

import static org.apache.commons.lang.StringUtils.isEmpty;
import static org.apache.commons.lang.StringUtils.isNotEmpty;
import javax.annotation.CheckForNull;
import javax.annotation.Nullable;

import org.joda.time.DateTime;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.DataBoundSetter;
import org.kohsuke.stapler.QueryParameter;

import com.atlassian.jira.rest.client.api.JiraRestClient;
import com.atlassian.jira.rest.client.api.RestClientException;
import com.atlassian.jira.rest.client.api.domain.Issue;
import com.atlassian.jira.rest.client.api.domain.IssueType;
import com.atlassian.jira.rest.client.api.domain.Version;
import com.atlassian.jira.rest.client.internal.async.AsynchronousJiraRestClientFactory;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;

import hudson.Extension;
import hudson.Util;
import hudson.model.AbstractDescribableImpl;
import hudson.model.Descriptor;
import hudson.model.Job;
import hudson.util.FormValidation;
import hudson.util.Secret;

/**
* Represents an external JIRA installation and configuration
@@ -62,7 +66,13 @@ public class JiraSite extends AbstractDescribableImpl<JiraSite> {
* See issue JENKINS-729, JENKINS-4092
*/
protected static final Pattern DEFAULT_ISSUE_PATTERN = Pattern.compile("([a-zA-Z][a-zA-Z0-9_]+-[1-9][0-9]*)([^.]|\\.[^0-9]|\\.$|$)");


/**
* Default rest api client calls timeout, in seconds
* See issue JENKINS-31113
*/
public static final int DEFAULT_TIMEOUT = 10;

/**
* URL of JIRA for Jenkins access, like <tt>http://jira.codehaus.org/</tt>.
* Mandatory. Normalized to end with '/'
@@ -127,7 +137,11 @@ public class JiraSite extends AbstractDescribableImpl<JiraSite> {
* @since 1.22
*/
public final boolean updateJiraIssueForAllStatus;


/**
* timeout used when calling jira rest api, in seconds
*/
public Integer timeout;

/**
* List of project keys (i.e., "MNG" portion of "MNG-512"),
@@ -163,13 +177,16 @@ public JiraSite(URL url, URL alternativeUrl, String userName, String password, b
throw new AssertionError(e);
}

this.url = url;
this.url = url;
this.timeout = JiraSite.DEFAULT_TIMEOUT;

this.alternativeUrl = alternativeUrl;
this.userName = Util.fixEmpty(userName);
this.password = Secret.fromString(Util.fixEmpty(password));
this.supportsWikiStyleComment = supportsWikiStyleComment;
this.recordScmChanges = recordScmChanges;
this.userPattern = Util.fixEmpty(userPattern);

if (this.userPattern != null) {
this.userPat = Pattern.compile(this.userPattern);
} else {
@@ -183,6 +200,11 @@ public JiraSite(URL url, URL alternativeUrl, String userName, String password, b
this.jiraSession = null;
}

@DataBoundSetter
public void setTimeout(Integer timeout) {
this.timeout = timeout;
}

protected Object readResolve() {
projectUpdateLock = new ReentrantLock();
issueCache = makeIssueCache();
@@ -230,12 +252,12 @@ protected JiraSession createSession() throws IOException {
LOGGER.warning("convert URL to URI error: " + e.getMessage());
throw new RuntimeException("failed to create JiraSession due to convert URI error");
}
LOGGER.fine("creating Jira Session: " + uri);
LOGGER.fine("creating Jira Session: " + uri);

final JiraRestClient jiraRestClient = new AsynchronousJiraRestClientFactory()
.createWithBasicHttpAuthentication(uri, userName, password.getPlainText());

return new JiraSession(this, new JiraRestService(uri, jiraRestClient, userName, password.getPlainText()));
int usedTimeout = timeout != null ? timeout : JiraSite.DEFAULT_TIMEOUT;
return new JiraSession(this, new JiraRestService(uri, jiraRestClient, userName, password.getPlainText(), usedTimeout));
}

/**
@@ -327,7 +349,7 @@ public Set<String> getProjectKeys() {
* @return null
* if no such was found.
*/
public static JiraSite get(AbstractProject<?, ?> p) {
public static JiraSite get(Job<?, ?> p) {
JiraProjectProperty jpp = p.getProperty(JiraProjectProperty.class);
if (jpp != null) {
JiraSite site = jpp.getSite();
@@ -662,7 +684,8 @@ public FormValidation doValidate(@QueryParameter String userName,
@QueryParameter String groupVisibility,
@QueryParameter String roleVisibility,
@QueryParameter boolean useHTTPAuth,
@QueryParameter String alternativeUrl) throws IOException {
@QueryParameter String alternativeUrl,
@QueryParameter Integer timeout) throws IOException {
url = Util.fixEmpty(url);
alternativeUrl = Util.fixEmpty(alternativeUrl);
URL mainURL, alternativeURL = null;
@@ -684,9 +707,9 @@ public FormValidation doValidate(@QueryParameter String userName,
return FormValidation.error(String.format("Malformed alternative URL (%s)",alternativeUrl), e );
}


JiraSite site = new JiraSite(mainURL, alternativeURL, userName, password, false,
false, null, false, groupVisibility, roleVisibility, useHTTPAuth);
site.setTimeout(timeout);
try {
JiraSession session = site.createSession();
session.getMyPermissions();
@@ -25,6 +25,9 @@
</f:entry>
<f:entry title="${%Password}" field="password" description="${%site.userpass}">
<f:password />
</f:entry>
<f:entry title="${%Connection timeout}" field="timeout">
<f:textbox default="10" />
</f:entry>
<f:entry title="${%Visible for Group}" field="groupVisibility">
<f:textbox />
@@ -34,7 +37,7 @@
</f:entry>
<f:entry>
<f:validateButton title="${%Validate Settings}"
method="validate" with="url,userName,password,groupVisibility,roleVisibility,useHTTPAuth,alternativeUrl" />
method="validate" with="url,userName,password,groupVisibility,roleVisibility,useHTTPAuth,alternativeUrl,timeout" />
</f:entry>
<f:entry title="">
<div align="right">
@@ -1,2 +1,3 @@
site.userpass=If REST API is not supported by JIRA, leave username/password empty.
site.alternativeUrl=JIRA alternative URL
site.alternativeUrl=JIRA alternative URL
site.timeout=in seconds
@@ -0,0 +1,3 @@
<div>
Connection timeout for JIRA REST API calls (in seconds)
</div>

0 comments on commit 06ec4d9

Please sign in to comment.