commit 87a057a3b8b2bcb45889d3af62fb79e8c5239b3c Author: Brian Long Date: Mon Jan 11 13:26:31 2021 -0500 initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e740936 --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +# Maven +target +pom.xml.versionsBackup + +# Eclipse +.project +.classpath +.settings + diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..7a1c868 --- /dev/null +++ b/pom.xml @@ -0,0 +1,93 @@ + + 4.0.0 + com.inteligr8 + bitbucket-api + jar + 1.0-SNAPSHOT + + BitBucket API & Utilities + + + 1.8 + 1.8 + UTF-8 + + + + + org.apache.httpcomponents + httpclient + 4.5.12 + + + com.fasterxml.jackson.jaxrs + jackson-jaxrs-json-provider + 2.11.0 + + + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + 2.11.0 + + + org.slf4j + slf4j-api + 1.7.30 + + + junit + junit + 4.13 + test + + + org.apache.logging.log4j + log4j-slf4j-impl + 2.13.2 + test + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + + **/*IT.class + + + + + org.apache.maven.plugins + maven-failsafe-plugin + + + integration-tests + integration-test + + integration-test + + + + **/*IT.class + + + + + + + + + + + inteligr8-releases + Inteligr8 Releases + http://repos.yateslong.us/nexus/repository/inteligr8-public + default + + + diff --git a/src/main/java/com/inteligr8/bitbucket/ApiGateway.java b/src/main/java/com/inteligr8/bitbucket/ApiGateway.java new file mode 100644 index 0000000..7760b33 --- /dev/null +++ b/src/main/java/com/inteligr8/bitbucket/ApiGateway.java @@ -0,0 +1,104 @@ +package com.inteligr8.bitbucket; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Map; +import java.util.Map.Entry; + +import org.apache.http.HttpResponse; +import org.apache.http.client.CredentialsProvider; +import org.apache.http.client.methods.HttpDelete; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.client.methods.HttpUriRequest; +import org.apache.http.client.methods.RequestBuilder; +import org.apache.http.entity.ContentType; +import org.apache.http.entity.StringEntity; +import org.apache.http.impl.client.HttpClientBuilder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.inteligr8.bitbucket.http.BaseResponse; +import com.inteligr8.bitbucket.http.PreemptiveAuthInterceptor; + +public class ApiGateway { + + private final Logger logger = LoggerFactory.getLogger(ApiGateway.class); + private final String baseUrl = "https://api.bitbucket.org/2.0"; + private ObjectMapper omapper = new ObjectMapper(); + private CredentialsProvider credProvider; + + public ApiGateway(CredentialsProvider credProvider) { + this.credProvider = credProvider; + } + + public Response get(String uriPath, Map paramMap, Class responseType) throws IOException { + return this.execute(HttpGet.METHOD_NAME, uriPath, paramMap, null, responseType); + } + + public Response post(String uriPath, Request requestObject, Class responseType) throws IOException { + return this.execute(HttpPost.METHOD_NAME, uriPath, null, requestObject, responseType); + } + + public Response put(String uriPath, Request requestObject, Class responseType) throws IOException { + return this.execute(HttpPost.METHOD_NAME, uriPath, null, requestObject, responseType); + } + + public Response delete(String uriPath, Map paramMap, Class responseType) throws IOException { + return this.execute(HttpDelete.METHOD_NAME, uriPath, paramMap, null, responseType); + } + + private Response execute(String method, String uriPath, Map paramMap, Request requestObject, Class responseType) throws IOException { + if (this.logger.isTraceEnabled()) + this.logger.trace("execute('" + method + "', '" + uriPath + "')"); + + RequestBuilder builder = RequestBuilder + .create(method) + .setUri(this.baseUrl + uriPath); + + if (paramMap != null) { + for (Entry param : paramMap.entrySet()) + if (param.getValue() != null) + builder.addParameter(param.getKey(), param.getValue().toString()); + } + + if (requestObject != null) { + String requestJson = this.omapper.writeValueAsString(requestObject); + if (this.logger.isTraceEnabled()) + this.logger.trace("execute('" + method + "', '" + uriPath + "'): " + requestJson); + + builder.setEntity(new StringEntity(requestJson, ContentType.APPLICATION_JSON)); + } + + HttpUriRequest request = builder.build(); + if (this.logger.isDebugEnabled()) + this.logger.debug("Prepared request for " + method + " to: " + uriPath); + + HttpResponse response = HttpClientBuilder + .create() + .addInterceptorFirst(new PreemptiveAuthInterceptor()) + .setDefaultCredentialsProvider(this.credProvider) + .build() + .execute(request); + if (this.logger.isDebugEnabled()) + this.logger.debug("Received response from " + method + ": " + response.getStatusLine().getStatusCode()); + + Response responseObject = null; + if (response.getEntity() != null) { + InputStream istream = response.getEntity().getContent(); + try { + responseObject = this.omapper.readerFor(responseType).readValue(istream); + } finally { + istream.close(); + } + } else { + responseObject = this.omapper.readerFor(responseType).readValue("{}"); + } + + responseObject.setHttpStatusCode(response.getStatusLine().getStatusCode()); + responseObject.setHttpStatusReason(response.getStatusLine().getReasonPhrase()); + return responseObject; + } + +} diff --git a/src/main/java/com/inteligr8/bitbucket/http/BaseResponse.java b/src/main/java/com/inteligr8/bitbucket/http/BaseResponse.java new file mode 100644 index 0000000..4faf7c3 --- /dev/null +++ b/src/main/java/com/inteligr8/bitbucket/http/BaseResponse.java @@ -0,0 +1,49 @@ +package com.inteligr8.bitbucket.http; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.inteligr8.bitbucket.model.Error; + +@JsonIgnoreProperties(ignoreUnknown = true) +public class BaseResponse { + + private int httpStatusCode; + private String httpStatusReason; + private String type; + private Error error; + + public int getHttpStatusCode() { + return this.httpStatusCode; + } + + public void setHttpStatusCode(int httpStatusCode) { + this.httpStatusCode = httpStatusCode; + } + + public String getHttpStatusReason() { + return this.httpStatusReason; + } + + public void setHttpStatusReason(String httpStatusReason) { + this.httpStatusReason = httpStatusReason; + } + + public String getType() { + return this.type; + } + + public void setType(String type) { + this.type = type; + } + + public Error getError() { + if (this.error == null) + this.error = new Error(); + return this.error; + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public class Data { + + } + +} diff --git a/src/main/java/com/inteligr8/bitbucket/http/PreemptiveAuthInterceptor.java b/src/main/java/com/inteligr8/bitbucket/http/PreemptiveAuthInterceptor.java new file mode 100644 index 0000000..97075dc --- /dev/null +++ b/src/main/java/com/inteligr8/bitbucket/http/PreemptiveAuthInterceptor.java @@ -0,0 +1,37 @@ +package com.inteligr8.bitbucket.http; + +import java.io.IOException; + +import org.apache.http.HttpException; +import org.apache.http.HttpHost; +import org.apache.http.HttpRequest; +import org.apache.http.HttpRequestInterceptor; +import org.apache.http.auth.AuthScope; +import org.apache.http.auth.AuthState; +import org.apache.http.auth.Credentials; +import org.apache.http.client.CredentialsProvider; +import org.apache.http.client.protocol.HttpClientContext; +import org.apache.http.impl.auth.BasicScheme; +import org.apache.http.protocol.HttpContext; +import org.apache.http.protocol.HttpCoreContext; + +public class PreemptiveAuthInterceptor implements HttpRequestInterceptor { + + public void process(final HttpRequest request, final HttpContext context) throws HttpException, IOException { + AuthState authState = (AuthState) context.getAttribute(HttpClientContext.TARGET_AUTH_STATE); + + // If no auth scheme available yet, try to initialize it + // preemptively + if (authState.getAuthScheme() == null) { + CredentialsProvider credsProvider = (CredentialsProvider) context.getAttribute(HttpClientContext.CREDS_PROVIDER); + HttpHost targetHost = (HttpHost) context.getAttribute(HttpCoreContext.HTTP_TARGET_HOST); + Credentials creds = credsProvider.getCredentials(new AuthScope(targetHost.getHostName(), targetHost.getPort())); + if (creds == null) { + throw new HttpException("No credentials for preemptive authentication"); + } + authState.update(new BasicScheme(), creds); + } + + } + +} diff --git a/src/main/java/com/inteligr8/bitbucket/model/Branch.java b/src/main/java/com/inteligr8/bitbucket/model/Branch.java new file mode 100644 index 0000000..6bd402e --- /dev/null +++ b/src/main/java/com/inteligr8/bitbucket/model/Branch.java @@ -0,0 +1,23 @@ +package com.inteligr8.bitbucket.model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonInclude.Include; + +@JsonInclude(value = Include.NON_EMPTY) +@JsonIgnoreProperties(ignoreUnknown = true) +public class Branch { + + @JsonProperty(required = true) + private String name; + + public String getName() { + return this.name; + } + + public void setName(String name) { + this.name = name; + } + +} diff --git a/src/main/java/com/inteligr8/bitbucket/model/CreateBranch.java b/src/main/java/com/inteligr8/bitbucket/model/CreateBranch.java new file mode 100644 index 0000000..ffc7628 --- /dev/null +++ b/src/main/java/com/inteligr8/bitbucket/model/CreateBranch.java @@ -0,0 +1,63 @@ +package com.inteligr8.bitbucket.model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.inteligr8.bitbucket.http.BaseResponse; + +public class CreateBranch { + + private CreateBranch() { + } + + public static String constructRequestPath(String repoName) { + return "/repositories/" + repoName + "/" + httpPath; + } + + public static String httpPath = "refs/branches"; + + @JsonIgnoreProperties(ignoreUnknown = true) + public static class Request { + + @JsonProperty(required = true) + private String name; + @JsonProperty(required = true) + private RequestTarget target; + + public String getName() { + return this.name; + } + + public void setName(String name) { + this.name = name; + } + + public RequestTarget getTarget() { + if (this.target == null) + this.target = new RequestTarget(); + return this.target; + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public class RequestTarget { + + @JsonProperty(required = true) + private String hash; + + public String getHash() { + return this.hash; + } + + public void setHash(String hash) { + this.hash = hash; + } + + } + + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static class Response extends BaseResponse { + + } + +} diff --git a/src/main/java/com/inteligr8/bitbucket/model/CreatePullRequest.java b/src/main/java/com/inteligr8/bitbucket/model/CreatePullRequest.java new file mode 100644 index 0000000..df2efdc --- /dev/null +++ b/src/main/java/com/inteligr8/bitbucket/model/CreatePullRequest.java @@ -0,0 +1,103 @@ +package com.inteligr8.bitbucket.model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.inteligr8.bitbucket.http.BaseResponse; + +public class CreatePullRequest { + + private CreatePullRequest() { + } + + public static String constructRequestPath(String repoName) { + return "/repositories/" + repoName + "/" + httpPath; + } + + public static String httpPath = "pullrequests"; + + @JsonInclude(value = Include.NON_EMPTY) + @JsonIgnoreProperties(ignoreUnknown = true) + public static class Request { + + @JsonProperty(required = true) + private String title; + private String description; + @JsonProperty(required = true) + private Repository source; + private Repository destination; + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getDescription() { + return this.description; + } + + public void setDescription(String description) { + this.description = description; + } + + public Repository getSource() { + if (this.source == null) + this.source = new Repository(); + return this.source; + } + + public Repository getDestination() { + if (this.destination == null) + this.destination = new Repository(); + return this.destination; + } + + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static class Response extends BaseResponse { + + private int id; + private String title; + private State state; + private Links links; + + public int getId() { + return this.id; + } + + public void setId(int id) { + this.id = id; + } + + public String getTitle() { + return this.title; + } + + public void setTitle(String title) { + this.title = title; + } + + public State getState() { + return this.state; + } + + public void setState(State state) { + this.state = state; + } + + public Links getLinks() { + if (this.links == null) + this.links = new Links(); + return this.links; + } + + public enum State {MERGED, SUPERSEDED, OPEN, DECLINED}; + + } + +} diff --git a/src/main/java/com/inteligr8/bitbucket/model/DeleteBranch.java b/src/main/java/com/inteligr8/bitbucket/model/DeleteBranch.java new file mode 100644 index 0000000..95dd590 --- /dev/null +++ b/src/main/java/com/inteligr8/bitbucket/model/DeleteBranch.java @@ -0,0 +1,14 @@ +package com.inteligr8.bitbucket.model; + +public class DeleteBranch { + + private DeleteBranch() { + } + + public static String constructRequestPath(String repoName, String branchName) { + return "/repositories/" + repoName + "/" + httpPath + "/" + branchName; + } + + public static String httpPath = "refs/branches"; + +} diff --git a/src/main/java/com/inteligr8/bitbucket/model/Error.java b/src/main/java/com/inteligr8/bitbucket/model/Error.java new file mode 100644 index 0000000..73bab62 --- /dev/null +++ b/src/main/java/com/inteligr8/bitbucket/model/Error.java @@ -0,0 +1,29 @@ +package com.inteligr8.bitbucket.model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +@JsonIgnoreProperties(ignoreUnknown = true) +public class Error { + + @JsonProperty(required = true) + private String message; + private String detail; + + public String getMessage() { + return this.message; + } + + public void setMessage(String message) { + this.message = message; + } + + public String getDetail() { + return this.detail; + } + + public void setDetail(String detail) { + this.detail = detail; + } + +} diff --git a/src/main/java/com/inteligr8/bitbucket/model/Link.java b/src/main/java/com/inteligr8/bitbucket/model/Link.java new file mode 100644 index 0000000..7116f08 --- /dev/null +++ b/src/main/java/com/inteligr8/bitbucket/model/Link.java @@ -0,0 +1,33 @@ +package com.inteligr8.bitbucket.model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonInclude.Include; + +@JsonInclude(value = Include.NON_EMPTY) +@JsonIgnoreProperties(ignoreUnknown = true) +public class Link { + + @JsonProperty(required = true) + private String name; + @JsonProperty(required = true) + private String href; + + public String getName() { + return this.name; + } + + public void setName(String name) { + this.name = name; + } + + public String getHref() { + return this.href; + } + + public void setHref(String href) { + this.href = href; + } + +} diff --git a/src/main/java/com/inteligr8/bitbucket/model/Links.java b/src/main/java/com/inteligr8/bitbucket/model/Links.java new file mode 100644 index 0000000..1d4aacf --- /dev/null +++ b/src/main/java/com/inteligr8/bitbucket/model/Links.java @@ -0,0 +1,26 @@ +package com.inteligr8.bitbucket.model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; + +@JsonInclude(value = Include.NON_EMPTY) +@JsonIgnoreProperties(ignoreUnknown = true) +public class Links { + + private Link self; + private Link html; + + public Link getSelf() { + if (this.self == null) + this.self = new Link(); + return this.self; + } + + public Link getHtml() { + if (this.html == null) + this.html = new Link(); + return this.html; + } + +} diff --git a/src/main/java/com/inteligr8/bitbucket/model/Repository.java b/src/main/java/com/inteligr8/bitbucket/model/Repository.java new file mode 100644 index 0000000..df24e75 --- /dev/null +++ b/src/main/java/com/inteligr8/bitbucket/model/Repository.java @@ -0,0 +1,21 @@ +package com.inteligr8.bitbucket.model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonInclude.Include; + +@JsonInclude(value = Include.NON_EMPTY) +@JsonIgnoreProperties(ignoreUnknown = true) +public class Repository { + + @JsonProperty(required = true) + private Branch branch; + + public Branch getBranch() { + if (this.branch == null) + this.branch = new Branch(); + return this.branch; + } + +} diff --git a/src/main/java/com/inteligr8/bitbucket/model/TextBox.java b/src/main/java/com/inteligr8/bitbucket/model/TextBox.java new file mode 100644 index 0000000..599d6d4 --- /dev/null +++ b/src/main/java/com/inteligr8/bitbucket/model/TextBox.java @@ -0,0 +1,41 @@ +package com.inteligr8.bitbucket.model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; + +@JsonInclude(value = Include.NON_EMPTY) +@JsonIgnoreProperties(ignoreUnknown = true) +public class TextBox { + + private String raw; + private Markup markup; + private String html; + + public String getRaw() { + return this.raw; + } + + public void setRaw(String raw) { + this.raw = raw; + } + + public Markup getMarkup() { + return this.markup; + } + + public void setMarkup(Markup markup) { + this.markup = markup; + } + + public String getHtml() { + return this.html; + } + + public void setHtml(String html) { + this.html = html; + } + + public enum Markup {markdown, creole, plaintext}; + +} diff --git a/src/main/java/com/inteligr8/bitbucket/model/TextBoxes.java b/src/main/java/com/inteligr8/bitbucket/model/TextBoxes.java new file mode 100644 index 0000000..ccf45dc --- /dev/null +++ b/src/main/java/com/inteligr8/bitbucket/model/TextBoxes.java @@ -0,0 +1,33 @@ +package com.inteligr8.bitbucket.model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; + +@JsonInclude(value = Include.NON_EMPTY) +@JsonIgnoreProperties(ignoreUnknown = true) +public class TextBoxes { + + private TextBox title; + private TextBox description; + private TextBox reason; + + public TextBox getTitle() { + if (this.title == null) + this.title = new TextBox(); + return this.title; + } + + public TextBox getDescription() { + if (this.description == null) + this.description = new TextBox(); + return this.description; + } + + public TextBox getReason() { + if (this.reason == null) + this.reason = new TextBox(); + return this.reason; + } + +} diff --git a/src/test/resources/log4j2.xml b/src/test/resources/log4j2.xml new file mode 100644 index 0000000..7a59c70 --- /dev/null +++ b/src/test/resources/log4j2.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + +