package hudson.plugins.tfs.util;
import com.cloudbees.plugins.credentials.common.StandardUsernamePasswordCredentials;
import com.microsoft.tfs.core.httpclient.HttpClient;
import com.microsoft.tfs.util.StringUtil;
import com.microsoft.visualstudio.services.webapi.patch.Operation;
import hudson.plugins.tfs.TeamCollectionConfiguration;
import hudson.plugins.tfs.model.GitCodePushedEventArgs;
import hudson.plugins.tfs.model.HttpMethod;
import hudson.plugins.tfs.model.JsonPatchOperation;
import hudson.plugins.tfs.model.Link;
import hudson.plugins.tfs.model.ListOfGitRepositories;
import hudson.plugins.tfs.model.PullRequestMergeCommitCreatedEventArgs;
import hudson.plugins.tfs.model.Server;
import hudson.plugins.tfs.model.TeamGitStatus;
import hudson.plugins.tfs.model.WorkItem;
import hudson.util.Secret;
import net.sf.json.JSON;
import net.sf.json.JSONArray;
import net.sf.json.JSONObject;
import org.apache.commons.io.IOUtils;
import javax.xml.bind.DatatypeConverter;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URI;
public class TeamRestClient {
private static final String AUTHORIZATION = "Authorization";
private static final String API_VERSION = "api-version";
private static final String NEW_LINE = System.getProperty("line.separator");
private final URI collectionUri;
private final boolean isTeamServices;
private final String authorization;
private final Server server;
public TeamRestClient(final URI collectionUri) throws IOException {
this(collectionUri, TeamCollectionConfiguration.findCredentialsForCollection(collectionUri));
}
public TeamRestClient(final URI collectionUri, final StandardUsernamePasswordCredentials credentials) throws IOException {
this.collectionUri = collectionUri;
final String hostName = collectionUri.getHost();
this.server = Server.create(null, null, collectionUri.toString(), credentials, null, null);
isTeamServices = TeamCollectionConfiguration.isTeamServices(hostName);
if (isTeamServices & credentials != null) {
authorization = createAuthorization(credentials);
}
else {
authorization = null;
}
}
public TeamRestClient(final String collectionUri, final StandardUsernamePasswordCredentials credentials) throws IOException {
this(URI.create(collectionUri), credentials);
}
static String createAuthorization(final StandardUsernamePasswordCredentials credentials) {
final String username = credentials.getUsername();
final Secret secretPassword = credentials.getPassword();
final String password = secretPassword.getPlainText();
final String credPair = username + ":" + password;
final byte[] credBytes = credPair.getBytes(MediaType.UTF_8);
final String base64enc = DatatypeConverter.printBase64Binary(credBytes);
final String result = "Basic " + base64enc;
return result;
}
protected <TRequest, TResponse> TResponse request(
final Class<TResponse> responseClass,
final HttpMethod httpMethod,
final URI requestUri,
final TRequest requestBody
) throws IOException {
final HttpClient httpClient = server.getHttpClient();
final String stringRequestBody;
if (requestBody != null) {
final JSON jsonObject;
if (requestBody instanceof JSON) {
jsonObject = (JSON) requestBody;
}
else {
jsonObject = JSONObject.fromObject(requestBody);
}
stringRequestBody = jsonObject.toString(0);
}
else {
stringRequestBody = null;
}
final com.microsoft.tfs.core.httpclient.HttpMethod clientMethod = httpMethod.createClientMethod(requestUri.toString(), stringRequestBody);
if (authorization != null) {
clientMethod.addRequestHeader(AUTHORIZATION, authorization);
}
final String stringResponseBody = innerRequest(clientMethod, httpClient);
if (responseClass == Void.class) {
return null;
}
if (responseClass == String.class) {
return (TResponse) stringResponseBody;
}
final TResponse result = deserialize(responseClass, stringResponseBody);
return result;
}
public static <TResponse> TResponse deserialize(final Class<TResponse> responseClass, final String stringResponseBody) {
try {
return EndpointHelper.MAPPER.readValue(stringResponseBody, responseClass);
}
catch (final IOException e) {
throw new Error(e);
}
}
static String innerRequest(final com.microsoft.tfs.core.httpclient.HttpMethod clientMethod, final HttpClient httpClient) throws IOException {
final int httpStatus = httpClient.executeMethod(clientMethod);
final String stringResult;
InputStream responseStream = null;
try {
if (httpStatus >= HttpURLConnection.HTTP_BAD_REQUEST) {
responseStream = clientMethod.getResponseBodyAsStream();
final String responseText = readResponseText(responseStream);
final StringBuilder sb = new StringBuilder("HTTP ").append(httpStatus);
final String statusText = clientMethod.getStatusText();
if (statusText != null) {
sb.append(" (").append(statusText).append(")");
}
if (!StringUtil.isNullOrEmpty(responseText)) {
sb.append(": ").append(responseText);
}
throw new IOException(sb.toString());
}
responseStream = clientMethod.getResponseBodyAsStream();
stringResult = readResponseText(responseStream);
}
finally {
IOUtils.closeQuietly(responseStream);
}
return stringResult;
}
static String readResponseText(final InputStream inputStream) throws IOException {
final InputStreamReader isr = new InputStreamReader(inputStream);
final BufferedReader reader = new BufferedReader(isr);
final StringBuilder sb = new StringBuilder();
try {
String line;
while ((line = reader.readLine()) != null) {
sb.append(line);
sb.append(NEW_LINE);
}
}
finally {
IOUtils.closeQuietly(reader);
IOUtils.closeQuietly(isr);
IOUtils.closeQuietly(inputStream);
}
return sb.toString();
}
public String ping() throws IOException {
final URI requestUri;
if (isTeamServices) {
requestUri = UriHelper.join(collectionUri, "_apis", "connectiondata");
}
else {
requestUri = UriHelper.join(collectionUri, "_home", "About");
}
return request(String.class, HttpMethod.GET, requestUri, null);
}
public ListOfGitRepositories getRepositories() throws IOException {
final QueryString qs = new QueryString(API_VERSION, "1.0");
final URI requestUri = UriHelper.join(
collectionUri,
"_apis",
"git",
"repositories",
qs
);
return request(ListOfGitRepositories.class, HttpMethod.GET, requestUri, null);
}
public TeamGitStatus addCommitStatus(final GitCodePushedEventArgs args, final TeamGitStatus status) throws IOException {
final QueryString qs = new QueryString(API_VERSION, "2.1");
final URI requestUri = UriHelper.join(
collectionUri, args.projectId,
"_apis", "git",
"repositories", args.repoId,
"commits", args.commit,
"statuses",
qs);
return request(TeamGitStatus.class, HttpMethod.POST, requestUri, status);
}
public WorkItem getWorkItem(final int workItemId) throws IOException {
final QueryString qs = new QueryString(API_VERSION, "1.0");
final URI requestUri = UriHelper.join(
collectionUri,
"_apis",
"wit",
"workitems",
workItemId,
qs
);
return request(WorkItem.class, HttpMethod.GET, requestUri, null);
}
public void addHyperlinkToWorkItem(final int workItemId, final String hyperlink) throws IOException {
final JSONArray doc = new JSONArray();
final WorkItem workItem = getWorkItem(workItemId);
final JsonPatchOperation testRev = new JsonPatchOperation();
testRev.setOp(Operation.TEST);
testRev.setPath("/rev");
testRev.setValue(workItem.getRev());
doc.add(testRev);
// TODO: do we also need to "add" to "/fields/System.History"?
final Link link = new Link("Hyperlink", hyperlink);
final JsonPatchOperation addRelation = new JsonPatchOperation();
addRelation.setOp(Operation.ADD);
addRelation.setPath("/relations/-");
addRelation.setValue(link);
doc.add(addRelation);
final QueryString qs = new QueryString(API_VERSION, "1.0");
final URI requestUri = UriHelper.join(
collectionUri,
"_apis",
"wit",
"workitems",
workItemId,
qs);
// TODO: this call could fail because something else bumped the rev in the meantime; retry?
request(Void.class, HttpMethod.PATCH, requestUri, doc);
}
public TeamGitStatus addPullRequestStatus(final PullRequestMergeCommitCreatedEventArgs args, final TeamGitStatus status) throws IOException {
final QueryString qs = new QueryString(API_VERSION, "3.0-preview.1");
final URI requestUri = UriHelper.join(
collectionUri, args.projectId,
"_apis", "git",
"repositories", args.repoId,
"pullRequests", args.pullRequestId,
"statuses",
qs);
return request(TeamGitStatus.class, HttpMethod.POST, requestUri, status);
}
public TeamGitStatus addPullRequestIterationStatus(final PullRequestMergeCommitCreatedEventArgs args, final TeamGitStatus status) throws IOException {
final QueryString qs = new QueryString(API_VERSION, "3.0-preview.1");
final URI requestUri = UriHelper.join(
collectionUri, args.projectId,
"_apis", "git",
"repositories", args.repoId,
"pullRequests", args.pullRequestId,
"iterations", args.iterationId,
"statuses",
qs);
return request(TeamGitStatus.class, HttpMethod.POST, requestUri, status);
}
}