diff --git a/pom.xml b/pom.xml index 17db303236..959ff2959e 100644 --- a/pom.xml +++ b/pom.xml @@ -342,6 +342,12 @@ + + src/main/java/**/*.java + src/main/java11/**/*.java + src/test/java/**/*.java + + ${basedir}/src/build/eclipse/formatter.xml diff --git a/src/main/java/org/kohsuke/github/AbstractBuilder.java b/src/main/java/org/kohsuke/github/AbstractBuilder.java index 51b5177032..553bb08466 100644 --- a/src/main/java/org/kohsuke/github/AbstractBuilder.java +++ b/src/main/java/org/kohsuke/github/AbstractBuilder.java @@ -37,6 +37,8 @@ * @param * Intermediate return type for this builder returned by calls to {@link #with(String, Object)}. If {@link S} * the same as {@link R}, this builder will commit changes after each call to {@link #with(String, Object)}. + * + * @author Liam Newman */ abstract class AbstractBuilder extends GitHubInteractiveObject { diff --git a/src/main/java/org/kohsuke/github/AbuseLimitHandler.java b/src/main/java/org/kohsuke/github/AbuseLimitHandler.java index 86be06db05..415e093a14 100644 --- a/src/main/java/org/kohsuke/github/AbuseLimitHandler.java +++ b/src/main/java/org/kohsuke/github/AbuseLimitHandler.java @@ -16,6 +16,7 @@ * GitHubBuilder#withAbuseLimitHandler(GitHubAbuseLimitHandler) * @see documentation * @see RateLimitHandler + * @deprecated Switch to {@link GitHubAbuseLimitHandler}. */ @Deprecated public abstract class AbuseLimitHandler extends GitHubAbuseLimitHandler { diff --git a/src/main/java/org/kohsuke/github/GHRateLimit.java b/src/main/java/org/kohsuke/github/GHRateLimit.java index 0177e4a2b0..96becb06b2 100644 --- a/src/main/java/org/kohsuke/github/GHRateLimit.java +++ b/src/main/java/org/kohsuke/github/GHRateLimit.java @@ -24,7 +24,7 @@ /** * Rate limit. * - * @author Kohsuke Kawaguchi + * @author Liam Newman */ @SuppressFBWarnings(value = "URF_UNREAD_PUBLIC_OR_PROTECTED_FIELD", justification = "JSON API") public class GHRateLimit { @@ -380,6 +380,7 @@ static void reset() { * A rate limit record. * * @since 1.100 + * @author Liam Newman */ public static class Record { /** diff --git a/src/main/java/org/kohsuke/github/GitHubAbuseLimitHandler.java b/src/main/java/org/kohsuke/github/GitHubAbuseLimitHandler.java index 60f2ad1859..81ca443978 100644 --- a/src/main/java/org/kohsuke/github/GitHubAbuseLimitHandler.java +++ b/src/main/java/org/kohsuke/github/GitHubAbuseLimitHandler.java @@ -13,6 +13,8 @@ * @author Kohsuke Kawaguchi * @see GitHubBuilder#withAbuseLimitHandler(AbuseLimitHandler) GitHubBuilder#withRateLimitHandler(AbuseLimitHandler) * @see GitHubRateLimitHandler + * + * @author Liam Newman */ public abstract class GitHubAbuseLimitHandler extends GitHubConnectorResponseErrorHandler { diff --git a/src/main/java/org/kohsuke/github/GitHubClient.java b/src/main/java/org/kohsuke/github/GitHubClient.java index feb9ee5d51..4e20e55c03 100644 --- a/src/main/java/org/kohsuke/github/GitHubClient.java +++ b/src/main/java/org/kohsuke/github/GitHubClient.java @@ -8,7 +8,7 @@ import org.kohsuke.github.connector.GitHubConnector; import org.kohsuke.github.connector.GitHubConnectorRequest; import org.kohsuke.github.connector.GitHubConnectorResponse; -import org.kohsuke.github.function.BodyHandler; +import org.kohsuke.github.function.FunctionThrows; import java.io.*; import java.net.*; @@ -33,9 +33,14 @@ /** * A GitHub API Client *

- * A GitHubClient can be used to send requests and retrieve their responses. GitHubClient is thread-safe and can be used - * to send multiple requests. GitHubClient also track some GitHub API information such as {@link GHRateLimit}. + * A GitHubClient can be used to send requests and retrieve their responses. Uses {@link GitHubConnector} as a pluggable + * component to communicate using differing HTTP client libraries. + *

+ * GitHubClient is thread-safe and can be used to send multiple requests simultaneously. GitHubClient also tracks some + * GitHub API information such as {@link GHRateLimit}. *

+ * + * @author Liam Newman */ class GitHubClient { @@ -764,4 +769,14 @@ static class RetryRequestException extends IOException { this.connectorRequest = connectorRequest; } } + + /** + * Represents a supplier of results that can throw. + * + * @param + * the type of results supplied by this supplier + */ + @FunctionalInterface + interface BodyHandler extends FunctionThrows { + } } diff --git a/src/main/java/org/kohsuke/github/GitHubConnectorResponseErrorHandler.java b/src/main/java/org/kohsuke/github/GitHubConnectorResponseErrorHandler.java index ad69abea79..da75944ac8 100644 --- a/src/main/java/org/kohsuke/github/GitHubConnectorResponseErrorHandler.java +++ b/src/main/java/org/kohsuke/github/GitHubConnectorResponseErrorHandler.java @@ -12,8 +12,7 @@ import static java.net.HttpURLConnection.HTTP_NOT_FOUND; /** - * Pluggable strategy to determine what to detect and choose what to do when various errors occur during an http - * request. + * Pluggable strategy to detect and choose what to do when errors occur during an http request. * * @author Liam Newman */ diff --git a/src/main/java/org/kohsuke/github/GitHubInteractiveObject.java b/src/main/java/org/kohsuke/github/GitHubInteractiveObject.java index 5c4e199dc2..e6f84db05f 100644 --- a/src/main/java/org/kohsuke/github/GitHubInteractiveObject.java +++ b/src/main/java/org/kohsuke/github/GitHubInteractiveObject.java @@ -13,6 +13,8 @@ * Ensures that all data references to GitHub connection are transient. * * Classes that do not need to interact with GitHub after they are instantiated do not need to inherit from this class. + * + * @author Liam Newman */ abstract class GitHubInteractiveObject { @JacksonInject diff --git a/src/main/java/org/kohsuke/github/GitHubPageContentsIterable.java b/src/main/java/org/kohsuke/github/GitHubPageContentsIterable.java index 6404f2290e..5468f8a5a7 100644 --- a/src/main/java/org/kohsuke/github/GitHubPageContentsIterable.java +++ b/src/main/java/org/kohsuke/github/GitHubPageContentsIterable.java @@ -14,6 +14,8 @@ * * @param * the type of items on each page + * + * @author Liam Newman */ class GitHubPageContentsIterable extends PagedIterable { diff --git a/src/main/java/org/kohsuke/github/GitHubPageIterator.java b/src/main/java/org/kohsuke/github/GitHubPageIterator.java index 476f5cb3cf..c2a5b42fef 100644 --- a/src/main/java/org/kohsuke/github/GitHubPageIterator.java +++ b/src/main/java/org/kohsuke/github/GitHubPageIterator.java @@ -19,6 +19,8 @@ * * @param * type of each page (not the items in the page). + * + * @author Liam Newman */ class GitHubPageIterator implements Iterator { diff --git a/src/main/java/org/kohsuke/github/GitHubRateLimitChecker.java b/src/main/java/org/kohsuke/github/GitHubRateLimitChecker.java index 859baf0ce5..4f24eb2445 100644 --- a/src/main/java/org/kohsuke/github/GitHubRateLimitChecker.java +++ b/src/main/java/org/kohsuke/github/GitHubRateLimitChecker.java @@ -26,6 +26,8 @@ * allows for a wide range of {@link RateLimitChecker} implementations, including complex throttling strategies and * polling. *

+ * + * @author Liam Newman */ class GitHubRateLimitChecker { diff --git a/src/main/java/org/kohsuke/github/GitHubRateLimitHandler.java b/src/main/java/org/kohsuke/github/GitHubRateLimitHandler.java index ec977a1f3d..d03ff54782 100644 --- a/src/main/java/org/kohsuke/github/GitHubRateLimitHandler.java +++ b/src/main/java/org/kohsuke/github/GitHubRateLimitHandler.java @@ -14,6 +14,8 @@ * @author Kohsuke Kawaguchi * @see GitHubBuilder#withRateLimitHandler(RateLimitHandler) GitHubBuilder#withRateLimitHandler(RateLimitHandler) * @see GitHubAbuseLimitHandler + * + * @author Liam Newman */ public abstract class GitHubRateLimitHandler extends GitHubConnectorResponseErrorHandler { diff --git a/src/main/java/org/kohsuke/github/GitHubRequest.java b/src/main/java/org/kohsuke/github/GitHubRequest.java index 91b64098de..3d19f085e4 100644 --- a/src/main/java/org/kohsuke/github/GitHubRequest.java +++ b/src/main/java/org/kohsuke/github/GitHubRequest.java @@ -27,12 +27,14 @@ /** * Class {@link GitHubRequest} represents an immutable instance used by the client to determine what information to - * retrieve from a GitHub server. Use the {@link Builder} construct a {@link GitHubRequest}. + * retrieve from a GitHub server. Use the {@link Builder} to construct a {@link GitHubRequest}. *

* NOTE: {@link GitHubRequest} should include the data type to be returned. Any use cases where the same request should * be used to return different types of data could be handled in some other way. However, the return type is currently * not specified until late in the building process, so this is still untyped. *

+ * + * @author Liam Newman */ public class GitHubRequest implements GitHubConnectorRequest { diff --git a/src/main/java/org/kohsuke/github/GitHubResponse.java b/src/main/java/org/kohsuke/github/GitHubResponse.java index 1cbda58ee5..ffb6608243 100644 --- a/src/main/java/org/kohsuke/github/GitHubResponse.java +++ b/src/main/java/org/kohsuke/github/GitHubResponse.java @@ -22,11 +22,13 @@ /** * A GitHubResponse *

- * A {@link GitHubResponse} generated by from sending a {@link GitHubRequest} to a {@link GitHubClient}. + * A {@link GitHubResponse} generated by sending a {@link GitHubRequest} to a {@link GitHubClient}. *

* * @param * the type of the data parsed from the body of a {@link GitHubConnectorResponse}. + * + * @author Liam Newman */ class GitHubResponse { diff --git a/src/main/java/org/kohsuke/github/RateLimitChecker.java b/src/main/java/org/kohsuke/github/RateLimitChecker.java index cf6ba55c18..c01576473d 100644 --- a/src/main/java/org/kohsuke/github/RateLimitChecker.java +++ b/src/main/java/org/kohsuke/github/RateLimitChecker.java @@ -16,6 +16,8 @@ * {@link RateLimitChecker} is called before each request to check the rate limit and wait if the checker criteria are * met. *

+ * + * @author Liam Newman */ public abstract class RateLimitChecker { diff --git a/src/main/java/org/kohsuke/github/RateLimitHandler.java b/src/main/java/org/kohsuke/github/RateLimitHandler.java index d5449dda88..07ea4f99ca 100644 --- a/src/main/java/org/kohsuke/github/RateLimitHandler.java +++ b/src/main/java/org/kohsuke/github/RateLimitHandler.java @@ -15,6 +15,7 @@ * @see GitHubBuilder#withRateLimitHandler(GitHubRateLimitHandler) * GitHubBuilder#withRateLimitHandler(GitHubRateLimitHandler) * @see AbuseLimitHandler + * @deprecated Switch to {@link GitHubRateLimitHandler} or even better provide {@link RateLimitChecker}s. */ @Deprecated public abstract class RateLimitHandler extends GitHubRateLimitHandler { diff --git a/src/main/java/org/kohsuke/github/Requester.java b/src/main/java/org/kohsuke/github/Requester.java index 2f3154ec09..54a3c9e86f 100644 --- a/src/main/java/org/kohsuke/github/Requester.java +++ b/src/main/java/org/kohsuke/github/Requester.java @@ -26,7 +26,6 @@ import edu.umd.cs.findbugs.annotations.NonNull; import org.apache.commons.io.IOUtils; import org.kohsuke.github.connector.GitHubConnectorResponse; -import org.kohsuke.github.function.BodyHandler; import org.kohsuke.github.function.InputStreamFunction; import java.io.ByteArrayInputStream; @@ -123,9 +122,9 @@ public T fetchStream(@Nonnull InputStreamFunction handler) throws IOExcep * * Copies an input stream to an in-memory input stream. The performance on this is not great but * {@link GitHubConnectorResponse#bodyStream()} is closed at the end of every call to - * {@link GitHubClient#sendRequest(GitHubRequest, BodyHandler)}, so any reads to the original input stream must be - * completed before then. There are a number of deprecated methods that return {@link InputStream}. This method - * keeps all of them using the same code path. + * {@link GitHubClient#sendRequest(GitHubRequest, GitHubClient.BodyHandler)}, so any reads to the original input + * stream must be completed before then. There are a number of deprecated methods that return {@link InputStream}. + * This method keeps all of them using the same code path. * * @param inputStream * the input stream to be copied diff --git a/src/main/java/org/kohsuke/github/authorization/AnonymousAuthorizationProvider.java b/src/main/java/org/kohsuke/github/authorization/AnonymousAuthorizationProvider.java new file mode 100644 index 0000000000..b71a42626a --- /dev/null +++ b/src/main/java/org/kohsuke/github/authorization/AnonymousAuthorizationProvider.java @@ -0,0 +1,15 @@ +package org.kohsuke.github.authorization; + +import java.io.IOException; + +/** + * A {@link AuthorizationProvider} that returns an empty authorization. + *

+ * This will result in the "Authorization" header not being added to a request. + */ +public class AnonymousAuthorizationProvider implements AuthorizationProvider { + @Override + public String getEncodedAuthorization() throws IOException { + return null; + } +} diff --git a/src/main/java/org/kohsuke/github/authorization/AuthorizationProvider.java b/src/main/java/org/kohsuke/github/authorization/AuthorizationProvider.java index 4dd615885d..bc6ddc1931 100644 --- a/src/main/java/org/kohsuke/github/authorization/AuthorizationProvider.java +++ b/src/main/java/org/kohsuke/github/authorization/AuthorizationProvider.java @@ -3,12 +3,17 @@ import java.io.IOException; /** - * Provides a functional interface that returns a valid encodedAuthorization. This strategy allows for a provider that - * dynamically changes the credentials. Each request will request the credentials from the provider. + * Provides a functional interface that returns a valid encodedAuthorization. + * + * This interface support the creation of providers based on immutable credentials or dynamic credentials which change + * of time. Each {@link org.kohsuke.github.connector.GitHubConnectorRequest} will call + * {@link #getEncodedAuthorization()} on the provider. + * + * @author Liam Newman */ public interface AuthorizationProvider { /** - * An static instance for an ANONYMOUS authorization provider + * A static instance for an ANONYMOUS authorization provider */ AuthorizationProvider ANONYMOUS = new AnonymousAuthorizationProvider(); @@ -27,17 +32,8 @@ public interface AuthorizationProvider { * * @return encoded authorization string, can be null * @throws IOException - * on any error that prevents the provider from getting a valid authorization + * on any error that prevents the provider from returning a valid authorization */ String getEncodedAuthorization() throws IOException; - /** - * A {@link AuthorizationProvider} that ensures that no credentials are returned - */ - class AnonymousAuthorizationProvider implements AuthorizationProvider { - @Override - public String getEncodedAuthorization() throws IOException { - return null; - } - } } diff --git a/src/main/java/org/kohsuke/github/authorization/ImmutableAuthorizationProvider.java b/src/main/java/org/kohsuke/github/authorization/ImmutableAuthorizationProvider.java index 41a113285a..20c780b9ba 100644 --- a/src/main/java/org/kohsuke/github/authorization/ImmutableAuthorizationProvider.java +++ b/src/main/java/org/kohsuke/github/authorization/ImmutableAuthorizationProvider.java @@ -7,7 +7,7 @@ import javax.annotation.CheckForNull; /** - * A {@link AuthorizationProvider} that always returns the same credentials + * An {@link AuthorizationProvider} that always returns the same credentials. */ public class ImmutableAuthorizationProvider implements AuthorizationProvider { @@ -99,6 +99,8 @@ public String getEncodedAuthorization() { /** * An internal class representing all user-related credentials, which are credentials that have a login or should * query the user endpoint for the login matching this credential. + * + * @see org.kohsuke.github.authorization.UserAuthorizationProvider UserAuthorizationProvider */ private static class UserProvider extends ImmutableAuthorizationProvider implements UserAuthorizationProvider { diff --git a/src/main/java/org/kohsuke/github/authorization/OrgAppInstallationAuthorizationProvider.java b/src/main/java/org/kohsuke/github/authorization/OrgAppInstallationAuthorizationProvider.java index 31af483eb9..d2437d3afb 100644 --- a/src/main/java/org/kohsuke/github/authorization/OrgAppInstallationAuthorizationProvider.java +++ b/src/main/java/org/kohsuke/github/authorization/OrgAppInstallationAuthorizationProvider.java @@ -13,7 +13,7 @@ import javax.annotation.Nonnull; /** - * Provides an AuthorizationProvider that performs automatic token refresh. + * An AuthorizationProvider that performs automatic token refresh for an organization's AppInstallation. */ public class OrgAppInstallationAuthorizationProvider extends GitHub.DependentAuthorizationProvider { diff --git a/src/main/java/org/kohsuke/github/connector/GitHubConnector.java b/src/main/java/org/kohsuke/github/connector/GitHubConnector.java index cbff11dbc5..63ffe9892d 100644 --- a/src/main/java/org/kohsuke/github/connector/GitHubConnector.java +++ b/src/main/java/org/kohsuke/github/connector/GitHubConnector.java @@ -7,7 +7,7 @@ import java.io.IOException; /** - * Pluggability for customizing HTTP request behaviors or using altogether different library. + * Interface for customizing HTTP request behaviors or using any HTTP client library for interacting with GitHub. * * @author Liam Newman */ @@ -15,10 +15,11 @@ public interface GitHubConnector { /** + * Sends a request and retrieves a raw response for processing. * * Implementers of {@link GitHubConnector#send(GitHubConnectorRequest)} process the information from a - * {@link GitHubConnectorRequest} to open an HTTP connection and retrieve a response. They then return a class that - * extends {@link GitHubConnectorResponse} for their response data. + * {@link GitHubConnectorRequest} to open an HTTP connection and retrieve a raw response. They then return a class + * that extends {@link GitHubConnectorResponse} corresponding their response data. * * Clients should not implement their own {@link GitHubConnectorRequest}. The {@link GitHubConnectorRequest} * provided by the caller of {@link GitHubConnector#send(GitHubConnectorRequest)} should be passed to the @@ -29,16 +30,18 @@ public interface GitHubConnector { * @return a GitHubConnectorResponse for the request * @throws IOException * if there is an I/O error + * + * @author Liam Newman */ GitHubConnectorResponse send(GitHubConnectorRequest connectorRequest) throws IOException; /** * Default implementation used when connector is not set by user. * - * This calls {@link DefaultGitHubConnector#create} to get the default connector instance. The output of that method - * may differ depending on Java version and system properties. + * This calls {@link DefaultGitHubConnector#create()} to get the default connector instance. The output of that + * method may differ depending on Java version and system properties. * - * @see DefaultGitHubConnector#create + * @see DefaultGitHubConnector#create() DefaultGitHubConnector#create() */ GitHubConnector DEFAULT = DefaultGitHubConnector.create(); diff --git a/src/main/java/org/kohsuke/github/connector/GitHubConnectorRequest.java b/src/main/java/org/kohsuke/github/connector/GitHubConnectorRequest.java index 1b6e22f532..e00d59dcec 100644 --- a/src/main/java/org/kohsuke/github/connector/GitHubConnectorRequest.java +++ b/src/main/java/org/kohsuke/github/connector/GitHubConnectorRequest.java @@ -9,15 +9,17 @@ import javax.annotation.Nonnull; /** - * A request passed to {@link GitHubConnector#send(GitHubConnectorRequest)} to get a {@link GitHubConnectorResponse}.\ + * A request passed to {@link GitHubConnector#send(GitHubConnectorRequest)} to get a {@link GitHubConnectorResponse}. * * Implementers of {@link GitHubConnector#send(GitHubConnectorRequest)} process the information from a * {@link GitHubConnectorRequest} to open an HTTP connection and retrieve a response. They then return a class that - * extends {@link GitHubConnectorResponse} for their response data. + * extends {@link GitHubConnectorResponse} corresponding their response data. * * Clients should not implement their own {@link GitHubConnectorRequest}. The {@link GitHubConnectorRequest} provided by * the caller of {@link GitHubConnector#send(GitHubConnectorRequest)} should be passed to the constructor of * {@link GitHubConnectorResponse}. + * + * @author Liam Newman */ public interface GitHubConnectorRequest { diff --git a/src/main/java/org/kohsuke/github/connector/GitHubConnectorResponse.java b/src/main/java/org/kohsuke/github/connector/GitHubConnectorResponse.java index 1790a183d3..3700236074 100644 --- a/src/main/java/org/kohsuke/github/connector/GitHubConnectorResponse.java +++ b/src/main/java/org/kohsuke/github/connector/GitHubConnectorResponse.java @@ -1,12 +1,15 @@ package org.kohsuke.github.connector; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import org.apache.commons.io.IOUtils; +import java.io.ByteArrayInputStream; import java.io.Closeable; import java.io.IOException; import java.io.InputStream; import java.net.HttpURLConnection; import java.util.*; +import java.util.zip.GZIPInputStream; import javax.annotation.CheckForNull; import javax.annotation.Nonnull; @@ -14,9 +17,13 @@ /** * Response information supplied when a response is received and before the body is processed. *

- * Instances of this class are closed once the response is done being processed. This means that contents of the body - * stream will not be readable after a call is completed. Status code, response headers, and request details will still - * be readable but it is recommended that consumers copy any information they need rather than retaining a reference. + * Instances of this class are closed once the response is done being processed. This means that {@link #bodyStream()} + * will not be readable after a call is completed. + * + * {@link #statusCode()}, {@link #allHeaders()}, and {@link #request()} will still be readable but it is recommended + * that consumers copy any information they need rather than retaining a reference to {@link GitHubConnectorResponse}. + * + * @author Liam Newman */ public abstract class GitHubConnectorResponse implements Closeable { @@ -112,4 +119,78 @@ public int statusCode() { public Map> allHeaders() { return headers; } + + /** + * Handles the "Content-Encoding" header. + * + * @param stream + * the stream to possibly wrap + * @return an input stream potentially wrapped to decode gzip input + * @throws IOException + * if an I/O Exception occurs. + */ + protected InputStream wrapStream(InputStream stream) throws IOException { + String encoding = header("Content-Encoding"); + if (encoding == null || stream == null) + return stream; + if (encoding.equals("gzip")) + return new GZIPInputStream(stream); + + throw new UnsupportedOperationException("Unexpected Content-Encoding: " + encoding); + } + + public abstract static class ByteArrayResponse extends GitHubConnectorResponse { + + private boolean inputStreamRead = false; + private byte[] inputBytes = null; + private boolean isClosed = false; + + protected ByteArrayResponse(@Nonnull GitHubConnectorRequest request, + int statusCode, + @Nonnull Map> headers) { + super(request, statusCode, headers); + } + + /** + * {@inheritDoc} + */ + @Override + public InputStream bodyStream() throws IOException { + if (isClosed) { + throw new IOException("Response is closed"); + } + synchronized (this) { + if (!inputStreamRead) { + InputStream rawStream = rawBodyStream(); + try (InputStream stream = wrapStream(rawStream)) { + if (stream != null) { + inputBytes = IOUtils.toByteArray(stream); + } + } + inputStreamRead = true; + } + } + + return inputBytes == null ? null : new ByteArrayInputStream(inputBytes); + } + + /** + * Get the raw implementation specific body stream for this response. + * + * This method will only be called once to completion. If an exception is thrown, it may be called multiple + * times. + * + * @return the stream for the raw response + * @throws IOException + * if an I/O Exception occurs. + */ + @CheckForNull + protected abstract InputStream rawBodyStream() throws IOException; + + @Override + public void close() throws IOException { + isClosed = true; + this.inputBytes = null; + } + } } diff --git a/src/main/java/org/kohsuke/github/connector/GitHubConnectorResponseHttpUrlConnectionAdapter.java b/src/main/java/org/kohsuke/github/connector/GitHubConnectorResponseHttpUrlConnectionAdapter.java index 4ae6b83cbe..30ae888b90 100644 --- a/src/main/java/org/kohsuke/github/connector/GitHubConnectorResponseHttpUrlConnectionAdapter.java +++ b/src/main/java/org/kohsuke/github/connector/GitHubConnectorResponseHttpUrlConnectionAdapter.java @@ -15,6 +15,8 @@ * * Behavior is equivalent to a {@link HttpURLConnection} after {@link HttpURLConnection#connect()} has been called. * Methods that make no sense throw {@link UnsupportedOperationException}. + * + * @author Liam Newman */ @Deprecated class GitHubConnectorResponseHttpUrlConnectionAdapter extends HttpURLConnection { @@ -27,16 +29,6 @@ public GitHubConnectorResponseHttpUrlConnectionAdapter(GitHubConnectorResponse c this.connectorResponse = connectorResponse; } - /** - * Readable to support {@link GitHubConnectorResponse#fromHttpURLConnectionAdapter(HttpURLConnection)} which only - * exist for testing. - * - * @return - */ - GitHubConnectorResponse connectorResponse() { - return connectorResponse; - } - @Override public String getHeaderFieldKey(int n) { List keys = new ArrayList<>(connectorResponse.allHeaders().keySet()); diff --git a/src/main/java/org/kohsuke/github/extras/HttpClientGitHubConnector.java b/src/main/java/org/kohsuke/github/extras/HttpClientGitHubConnector.java index ea03c2319b..baa4365c8a 100644 --- a/src/main/java/org/kohsuke/github/extras/HttpClientGitHubConnector.java +++ b/src/main/java/org/kohsuke/github/extras/HttpClientGitHubConnector.java @@ -7,7 +7,7 @@ import java.io.IOException; /** - * {@link GitHubConnector} wrapper that sets timeout + * {@link GitHubConnector} for platforms that do not support Java 11 HttpClient. * * @author Liam Newman */ diff --git a/src/main/java/org/kohsuke/github/extras/okhttp3/OkHttpConnector.java b/src/main/java/org/kohsuke/github/extras/okhttp3/OkHttpConnector.java index abcfd15bc0..bc09891ea2 100644 --- a/src/main/java/org/kohsuke/github/extras/okhttp3/OkHttpConnector.java +++ b/src/main/java/org/kohsuke/github/extras/okhttp3/OkHttpConnector.java @@ -19,8 +19,7 @@ * 304 response does not count against the rate limit. See http://developer.github.com/v3/#conditional-requests * * @author Liam Newman - * @author Kohsuke Kawaguchi - * @deprecated Use OkHttpGitHubConnector instead. + * @deprecated Use {@link OkHttpGitHubConnector} instead. */ @Deprecated public class OkHttpConnector implements HttpConnector { diff --git a/src/main/java/org/kohsuke/github/extras/okhttp3/OkHttpGitHubConnector.java b/src/main/java/org/kohsuke/github/extras/okhttp3/OkHttpGitHubConnector.java index b83848906f..d61fde220b 100644 --- a/src/main/java/org/kohsuke/github/extras/okhttp3/OkHttpGitHubConnector.java +++ b/src/main/java/org/kohsuke/github/extras/okhttp3/OkHttpGitHubConnector.java @@ -7,25 +7,24 @@ import org.kohsuke.github.connector.GitHubConnectorRequest; import org.kohsuke.github.connector.GitHubConnectorResponse; -import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; -import java.util.zip.GZIPInputStream; +import javax.annotation.CheckForNull; import javax.annotation.Nonnull; /** - * {@link HttpConnector} for {@link OkHttpClient}. + * {@link GitHubConnector} for {@link OkHttpClient}. *

- * Unlike {@link #DEFAULT}, OkHttp does response caching. Making a conditional request against GitHubAPI and receiving a - * 304 response does not count against the rate limit. See http://developer.github.com/v3/#conditional-requests + * Unlike {@link #DEFAULT}, OkHttp supports response caching. Making a conditional request against GitHub API and + * receiving a 304 response does not count against the rate limit. See + * http://developer.github.com/v3/#conditional-requests * * @author Liam Newman - * @author Kohsuke Kawaguchi */ public class OkHttpGitHubConnector implements GitHubConnector { private static final String HEADER_NAME = "Cache-Control"; @@ -105,10 +104,7 @@ private List TlsConnectionSpecs() { * * Implementation specific to {@link okhttp3.Response}. */ - private static class OkHttpGitHubConnectorResponse extends GitHubConnectorResponse { - - private boolean bodyBytesRead = false; - private byte[] bodyBytes = null; + private static class OkHttpGitHubConnectorResponse extends GitHubConnectorResponse.ByteArrayResponse { @Nonnull private final Response response; @@ -118,51 +114,20 @@ private static class OkHttpGitHubConnectorResponse extends GitHubConnectorRespon this.response = response; } - /** - * {@inheritDoc} - */ + @CheckForNull @Override - public InputStream bodyStream() throws IOException { - readBodyBytes(); - InputStream stream = bodyBytes == null ? null : new ByteArrayInputStream(bodyBytes); - return stream; - } - - private void readBodyBytes() throws IOException { - synchronized (this) { - if (!bodyBytesRead) { - try (ResponseBody body = response.body()) { - if (body != null) { - try (InputStream stream = wrapStream(body.byteStream())) { - if (stream != null) { - bodyBytes = IOUtils.toByteArray(stream); - } - } - } - } - bodyBytesRead = true; - } + protected InputStream rawBodyStream() throws IOException { + ResponseBody body = response.body(); + if (body != null) { + return body.byteStream(); + } else { + return null; } } - /** - * Handles the "Content-Encoding" header. - * - * @param stream - * the stream to possibly wrap - */ - private InputStream wrapStream(InputStream stream) throws IOException { - String encoding = header("Content-Encoding"); - if (encoding == null || stream == null) - return stream; - if (encoding.equals("gzip")) - return new GZIPInputStream(stream); - - throw new UnsupportedOperationException("Unexpected Content-Encoding: " + encoding); - } - @Override public void close() throws IOException { + super.close(); response.close(); } } diff --git a/src/main/java/org/kohsuke/github/function/BodyHandler.java b/src/main/java/org/kohsuke/github/function/BodyHandler.java deleted file mode 100644 index 0083dcd526..0000000000 --- a/src/main/java/org/kohsuke/github/function/BodyHandler.java +++ /dev/null @@ -1,15 +0,0 @@ -package org.kohsuke.github.function; - -import org.kohsuke.github.connector.GitHubConnectorResponse; - -import java.io.IOException; - -/** - * Represents a supplier of results that can throw. - * - * @param - * the type of results supplied by this supplier - */ -@FunctionalInterface -public interface BodyHandler extends FunctionThrows { -} diff --git a/src/main/java/org/kohsuke/github/internal/DefaultGitHubConnector.java b/src/main/java/org/kohsuke/github/internal/DefaultGitHubConnector.java index 899294c2dd..1f7d488036 100644 --- a/src/main/java/org/kohsuke/github/internal/DefaultGitHubConnector.java +++ b/src/main/java/org/kohsuke/github/internal/DefaultGitHubConnector.java @@ -10,7 +10,9 @@ /** * Internal class that selects what kind of {@link GitHubConnector} will be the default. * - * Allow behavior to be changed for different version of Java, such as supporting Java 11 HttpClient. + * Allows behavior to be changed for different versions of Java, such as supporting Java 11 HttpClient. + * + * @author Liam Newman */ public final class DefaultGitHubConnector { diff --git a/src/main/java/org/kohsuke/github/internal/GitHubConnectorHttpConnectorAdapter.java b/src/main/java/org/kohsuke/github/internal/GitHubConnectorHttpConnectorAdapter.java index b4a55dab1f..01417e08dd 100644 --- a/src/main/java/org/kohsuke/github/internal/GitHubConnectorHttpConnectorAdapter.java +++ b/src/main/java/org/kohsuke/github/internal/GitHubConnectorHttpConnectorAdapter.java @@ -7,7 +7,6 @@ import org.kohsuke.github.connector.GitHubConnectorRequest; import org.kohsuke.github.connector.GitHubConnectorResponse; -import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.lang.reflect.Field; @@ -16,17 +15,16 @@ import java.net.URL; import java.util.List; import java.util.Map; -import java.util.logging.Logger; -import java.util.zip.GZIPInputStream; +import javax.annotation.CheckForNull; import javax.annotation.Nonnull; -import static java.util.logging.Level.*; - /** * Adapts an HttpConnector to be usable as GitHubConnector. * * For internal use only. + * + * @author Liam Newman */ public final class GitHubConnectorHttpConnectorAdapter implements GitHubConnector, HttpConnector { @@ -150,15 +148,13 @@ private static void setRequestMethod(String method, HttpURLConnection connection } /** - * Initial response information supplied to a {@link org.kohsuke.github.function.BodyHandler} when a response is - * initially received and before the body is processed. + * Initial response information supplied when a response is received but before the body is processed. * * Implementation specific to {@link HttpURLConnection}. For internal use only. */ - public final static class HttpURLConnectionGitHubConnectorResponse extends GitHubConnectorResponse { - - private boolean inputStreamRead = false; - private byte[] inputBytes = null; + public final static class HttpURLConnectionGitHubConnectorResponse + extends + GitHubConnectorResponse.ByteArrayResponse { @Nonnull private final HttpURLConnection connection; @@ -171,26 +167,14 @@ public final static class HttpURLConnectionGitHubConnectorResponse extends GitHu this.connection = connection; } - /** - * {@inheritDoc} - */ - public InputStream bodyStream() throws IOException { - synchronized (this) { - if (!inputStreamRead) { - InputStream rawStream = connection.getErrorStream(); - if (rawStream == null) { - rawStream = connection.getInputStream(); - } - try (InputStream stream = wrapStream(rawStream)) { - if (stream != null) { - inputBytes = IOUtils.toByteArray(stream); - } - } - inputStreamRead = true; - } + @CheckForNull + @Override + protected InputStream rawBodyStream() throws IOException { + InputStream rawStream = connection.getErrorStream(); + if (rawStream == null) { + rawStream = connection.getInputStream(); } - - return inputBytes == null ? null : new ByteArrayInputStream(inputBytes); + return rawStream; } /** @@ -205,28 +189,13 @@ public HttpURLConnection toHttpURLConnection() { return connection; } - /** - * Handles the "Content-Encoding" header. - * - * @param stream - * the stream to possibly wrap - * - */ - private InputStream wrapStream(InputStream stream) throws IOException { - String encoding = header("Content-Encoding"); - if (encoding == null || stream == null) - return stream; - if (encoding.equals("gzip")) - return new GZIPInputStream(stream); - - throw new UnsupportedOperationException("Unexpected Content-Encoding: " + encoding); - } - - private static final Logger LOGGER = Logger.getLogger(GitHub.class.getName()); - @Override public void close() throws IOException { - IOUtils.closeQuietly(connection.getInputStream()); + super.close(); + try { + IOUtils.closeQuietly(connection.getInputStream()); + } catch (IOException e) { + } } } diff --git a/src/main/java11/org/kohsuke/github/extras/HttpClientGitHubConnector.java b/src/main/java11/org/kohsuke/github/extras/HttpClientGitHubConnector.java index c8df48ca14..1e2a0cfce0 100644 --- a/src/main/java11/org/kohsuke/github/extras/HttpClientGitHubConnector.java +++ b/src/main/java11/org/kohsuke/github/extras/HttpClientGitHubConnector.java @@ -1,13 +1,10 @@ package org.kohsuke.github.extras; +import org.apache.commons.io.IOUtils; import org.kohsuke.github.connector.GitHubConnector; import org.kohsuke.github.connector.GitHubConnectorRequest; import org.kohsuke.github.connector.GitHubConnectorResponse; -import org.apache.commons.io.IOUtils; - -import javax.annotation.Nonnull; -import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.io.InterruptedIOException; @@ -17,10 +14,12 @@ import java.net.http.HttpResponse; import java.util.List; import java.util.Map; -import java.util.zip.GZIPInputStream; + +import javax.annotation.CheckForNull; +import javax.annotation.Nonnull; /** - * {@link GitHubConnector} wrapper that sets timeout + * {@link GitHubConnector} for {@link HttpClient}. * * @author Liam Newman */ @@ -29,7 +28,7 @@ public class HttpClientGitHubConnector implements GitHubConnector { private final HttpClient client; /** - * Instantiates a new HttpClientGitHubConnector. + * Instantiates a new HttpClientGitHubConnector with a defaut HttpClient. */ public HttpClientGitHubConnector() { this(HttpClient.newHttpClient()); @@ -39,7 +38,7 @@ public HttpClientGitHubConnector() { * Instantiates a new HttpClientGitHubConnector. * * @param client - * the base + * the HttpClient to be used */ public HttpClientGitHubConnector(HttpClient client) { this.client = client; @@ -73,7 +72,7 @@ public GitHubConnectorResponse send(GitHubConnectorRequest connectorRequest) thr HttpResponse httpResponse = client.send(request, HttpResponse.BodyHandlers.ofInputStream()); return new HttpClientGitHubConnectorResponse(connectorRequest, httpResponse); } catch (InterruptedException e) { - throw (InterruptedIOException)new InterruptedIOException(e.getMessage()).initCause(e); + throw (InterruptedIOException) new InterruptedIOException(e.getMessage()).initCause(e); } } @@ -82,61 +81,27 @@ public GitHubConnectorResponse send(GitHubConnectorRequest connectorRequest) thr * * Implementation specific to {@link HttpResponse}. */ - private static class HttpClientGitHubConnectorResponse extends GitHubConnectorResponse { - private boolean bodyBytesRead = false; - private byte[] bodyBytes = null; + private static class HttpClientGitHubConnectorResponse extends GitHubConnectorResponse.ByteArrayResponse { @Nonnull private final HttpResponse response; - protected HttpClientGitHubConnectorResponse(@Nonnull GitHubConnectorRequest request, @Nonnull HttpResponse response) { + protected HttpClientGitHubConnectorResponse(@Nonnull GitHubConnectorRequest request, + @Nonnull HttpResponse response) { super(request, response.statusCode(), response.headers().map()); this.response = response; } - /** - * {@inheritDoc} - */ + @CheckForNull @Override - public InputStream bodyStream() throws IOException { - readBodyBytes(); - InputStream stream = bodyBytes == null ? null : new ByteArrayInputStream(bodyBytes); - return stream; - } - - private void readBodyBytes() throws IOException { - synchronized (this) { - if (!bodyBytesRead) { - try (InputStream stream = wrapStream(response.body())) { - if (stream != null) { - bodyBytes = IOUtils.toByteArray(stream); - } - } - bodyBytesRead = true; - } - } - } - - /** - * Handles the "Content-Encoding" header. - * - * @param stream - * the stream to possibly wrap - * - */ - private InputStream wrapStream(InputStream stream) throws IOException { - String encoding = header("Content-Encoding"); - if (encoding == null || stream == null) - return stream; - if (encoding.equals("gzip")) - return new GZIPInputStream(stream); - - throw new UnsupportedOperationException("Unexpected Content-Encoding: " + encoding); + protected InputStream rawBodyStream() throws IOException { + return response.body(); } @Override public void close() throws IOException { - IOUtils.closeQuietly(response.body()); + super.close(); + IOUtils.closeQuietly(response.body()); } } } diff --git a/src/test/java/org/kohsuke/github/AbuseLimitHandlerTest.java b/src/test/java/org/kohsuke/github/AbuseLimitHandlerTest.java index d496cc9aea..bbd0751147 100644 --- a/src/test/java/org/kohsuke/github/AbuseLimitHandlerTest.java +++ b/src/test/java/org/kohsuke/github/AbuseLimitHandlerTest.java @@ -53,12 +53,13 @@ protected WireMockConfiguration getWireMockOptions() { public void testHandler_Fail() throws Exception { // Customized response that templates the date to keep things working snapshotNotAllowed(); + final HttpURLConnection[] savedConnection = new HttpURLConnection[1]; gitHub = getGitHubBuilder().withEndpoint(mockGitHub.apiServer().baseUrl()) .withAbuseLimitHandler(new AbuseLimitHandler() { @Override public void onError(IOException e, HttpURLConnection uc) throws IOException { - + savedConnection[0] = uc; // Verify assertThat(uc.getDate(), Matchers.greaterThanOrEqualTo(new Date().getTime() - 10000)); assertThat(uc.getExpiration(), equalTo(0L)); @@ -103,7 +104,6 @@ public void onError(IOException e, HttpURLConnection uc) throws IOException { assertThat(errorStream, notNullValue()); String errorString = IOUtils.toString(errorStream, StandardCharsets.UTF_8); fail(); - assertThat(errorString, containsString("Must have push access to repository")); } catch (IOException ex) { assertThat(ex, notNullValue()); assertThat(ex.getMessage(), containsString("stream is closed")); @@ -196,6 +196,11 @@ public void onError(IOException e, HttpURLConnection uc) throws IOException { assertThat(e.getMessage(), equalTo("Abuse limit reached")); } + if (savedConnection[0].toString().contains("GitHubConnectorResponseHttpUrlConnectionAdapter")) { + // error stream is non-null above. null here because response has been closed. + assertThat(savedConnection[0].getErrorStream(), nullValue()); + } + assertThat(mockGitHub.getRequestCount(), equalTo(2)); }