Skip to content

Commit 840cfbc

Browse files
authored
Merge branch 'main' into dependabot/maven/spring.boot.version-3.4.3
2 parents e6eae92 + 98cf5eb commit 840cfbc

15 files changed

+393
-118
lines changed

.github/workflows/maven-build.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ jobs:
107107
name: maven-test-target-directory
108108
path: target
109109
- name: Codecov Report
110-
uses: codecov/codecov-action@v5.3.1
110+
uses: codecov/codecov-action@v5.4.0
111111
with:
112112
# Codecov token from https://app.codecov.io/gh/hub4j/github-api/settings
113113
token: ${{ secrets.CODECOV_TOKEN }}

pom.xml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -411,7 +411,7 @@
411411
<dependency>
412412
<groupId>com.fasterxml.jackson</groupId>
413413
<artifactId>jackson-bom</artifactId>
414-
<version>2.18.2</version>
414+
<version>2.18.3</version>
415415
<scope>import</scope>
416416
<type>pom</type>
417417
</dependency>
@@ -562,7 +562,7 @@
562562
<dependency>
563563
<groupId>com.google.code.gson</groupId>
564564
<artifactId>gson</artifactId>
565-
<version>2.11.0</version>
565+
<version>2.12.1</version>
566566
<scope>test</scope>
567567
</dependency>
568568
<dependency>
@@ -611,7 +611,7 @@
611611
</configuration>
612612
</execution>
613613
<execution>
614-
<id>httpclient-test</id>
614+
<id>httpclient-test-tracing</id>
615615
<phase>integration-test</phase>
616616
<goals>
617617
<goal>test</goal>

src/main/java/org/kohsuke/github/GHRepository.java

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -835,7 +835,7 @@ public String getDefaultBranch() {
835835
*/
836836
@SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected")
837837
public GHRepository getTemplateRepository() {
838-
return (GHRepository) template_repository;
838+
return template_repository;
839839
}
840840

841841
/**
@@ -1503,6 +1503,21 @@ public GHPullRequest getPullRequest(int number) throws IOException {
15031503
.wrapUp(this);
15041504
}
15051505

1506+
/**
1507+
* Retrieves all the pull requests of a particular state.
1508+
*
1509+
* @param state
1510+
* the state
1511+
* @return the pull requests
1512+
* @throws IOException
1513+
* the io exception
1514+
* @deprecated Use {@link #queryPullRequests()}
1515+
*/
1516+
@Deprecated
1517+
public List<GHPullRequest> getPullRequests(GHIssueState state) throws IOException {
1518+
return queryPullRequests().state(state).list().toList();
1519+
}
1520+
15061521
/**
15071522
* Retrieves pull requests.
15081523
*

src/main/java/org/kohsuke/github/GitHubClient.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -629,6 +629,7 @@ private void logResponseBody(@Nonnull final GitHubConnectorResponse response) {
629629
LOGGER.log(FINEST, () -> {
630630
String body;
631631
try {
632+
response.setBodyStreamRereadable();
632633
body = GitHubResponse.getBodyAsString(response);
633634
} catch (Throwable e) {
634635
body = "Error reading response body";

src/main/java/org/kohsuke/github/connector/GitHubConnectorResponse.java

Lines changed: 128 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,25 @@
77
import java.io.Closeable;
88
import java.io.IOException;
99
import java.io.InputStream;
10-
import java.util.*;
10+
import java.util.ArrayList;
11+
import java.util.Collections;
12+
import java.util.Comparator;
13+
import java.util.List;
14+
import java.util.Map;
15+
import java.util.TreeMap;
1116
import java.util.zip.GZIPInputStream;
1217

1318
import javax.annotation.CheckForNull;
1419
import javax.annotation.Nonnull;
1520

21+
import static java.net.HttpURLConnection.HTTP_OK;
22+
1623
/**
1724
* Response information supplied when a response is received and before the body is processed.
1825
* <p>
26+
* During a request to GitHub, {@link GitHubConnector#send(GitHubConnectorRequest)} returns a
27+
* {@link GitHubConnectorResponse}. This is processed to create a GitHubResponse.
28+
* <p>
1929
* Instances of this class are closed once the response is done being processed. This means that {@link #bodyStream()}
2030
* will not be readable after a call is completed.
2131
*
@@ -35,6 +45,11 @@ public abstract class GitHubConnectorResponse implements Closeable {
3545
private final GitHubConnectorRequest request;
3646
@Nonnull
3747
private final Map<String, List<String>> headers;
48+
private boolean bodyStreamCalled = false;
49+
private InputStream bodyStream = null;
50+
private byte[] bodyBytes = null;
51+
private boolean isClosed = false;
52+
private boolean isBodyStreamRereadable;
3853

3954
/**
4055
* GitHubConnectorResponse constructor
@@ -58,6 +73,7 @@ protected GitHubConnectorResponse(@Nonnull GitHubConnectorRequest request,
5873
caseInsensitiveMap.put(entry.getKey(), Collections.unmodifiableList(new ArrayList<>(entry.getValue())));
5974
}
6075
this.headers = Collections.unmodifiableMap(caseInsensitiveMap);
76+
this.isBodyStreamRereadable = false;
6177
}
6278

6379
/**
@@ -79,17 +95,72 @@ public String header(String name) {
7995
/**
8096
* The response body as an {@link InputStream}.
8197
*
98+
* When {@link #isBodyStreamRereadable} is false, {@link #bodyStream()} can only be called once and the returned
99+
* stream should be assumed to be read-once and not resetable. This is the default behavior for HTTP_OK responses
100+
* and significantly reduces memory usage.
101+
*
102+
* When {@link #isBodyStreamRereadable} is true, {@link #bodyStream()} can be called be called multiple times. The
103+
* full stream data is read into a byte array during the first call. Each call returns a new stream backed by the
104+
* same byte array. This uses more memory, but is required to enable rereading the body stream during trace logging,
105+
* debugging, and error responses.
106+
*
82107
* @return the response body
83108
* @throws IOException
84109
* if response stream is null or an I/O Exception occurs.
85110
*/
86111
@Nonnull
87-
public abstract InputStream bodyStream() throws IOException;
112+
public InputStream bodyStream() throws IOException {
113+
synchronized (this) {
114+
if (isClosed) {
115+
throw new IOException("Response is closed");
116+
}
117+
118+
if (bodyStreamCalled) {
119+
if (!isBodyStreamRereadable()) {
120+
throw new IOException("Response body not rereadable");
121+
}
122+
} else {
123+
bodyStream = wrapStream(rawBodyStream());
124+
bodyStreamCalled = true;
125+
}
126+
127+
if (bodyStream == null) {
128+
throw new IOException("Response body missing, stream null");
129+
} else if (!isBodyStreamRereadable()) {
130+
return bodyStream;
131+
}
132+
133+
// Load rereadable byte array
134+
if (bodyBytes == null) {
135+
bodyBytes = IOUtils.toByteArray(bodyStream);
136+
// Close the raw body stream after successfully reading
137+
IOUtils.closeQuietly(bodyStream);
138+
}
139+
140+
return new ByteArrayInputStream(bodyBytes);
141+
}
142+
}
88143

89144
/**
90-
* Gets the {@link GitHubConnectorRequest} for this response.
145+
* Get the raw implementation specific body stream for this response.
146+
*
147+
* This method will only be called once to completion. If an exception is thrown by this method, it may be called
148+
* multiple times.
91149
*
92-
* @return the {@link GitHubConnectorRequest} for this response.
150+
* The stream returned from this method will be closed when the response is closed or sooner. Inheriting classes do
151+
* not need to close it.
152+
*
153+
* @return the stream for the raw response
154+
* @throws IOException
155+
* if an I/O Exception occurs.
156+
*/
157+
@CheckForNull
158+
protected abstract InputStream rawBodyStream() throws IOException;
159+
160+
/**
161+
* Gets the {@link GitHubConnector} for this response.
162+
*
163+
* @return the {@link GitHubConnector} for this response.
93164
*/
94165
@Nonnull
95166
public GitHubConnectorRequest request() {
@@ -116,6 +187,56 @@ public Map<String, List<String>> allHeaders() {
116187
return headers;
117188
}
118189

190+
/**
191+
* The body stream rereadable state.
192+
*
193+
* Body stream defaults to read once for HTTP_OK responses (to reduce memory usage). For non-HTTP_OK responses, body
194+
* stream is switched to rereadable (in-memory byte array) for error processing.
195+
*
196+
* Calling {@link #setBodyStreamRereadable()} will force {@link #isBodyStreamRereadable} to be true for this
197+
* response regardless of {@link #statusCode} value.
198+
*
199+
* @return true when body stream is rereadable.
200+
*/
201+
public boolean isBodyStreamRereadable() {
202+
synchronized (this) {
203+
return isBodyStreamRereadable || statusCode != HTTP_OK;
204+
}
205+
}
206+
207+
/**
208+
* Force body stream to rereadable regardless of status code.
209+
*
210+
* Calling {@link #setBodyStreamRereadable()} will force {@link #isBodyStreamRereadable} to be true for this
211+
* response regardless of {@link #statusCode} value.
212+
*
213+
* This is required to support body value logging during low-level tracing but should be avoided in general since it
214+
* consumes significantly more memory.
215+
*
216+
* Will throw runtime exception if a non-rereadable body stream has already been returned from
217+
* {@link #bodyStream()}.
218+
*/
219+
public void setBodyStreamRereadable() {
220+
synchronized (this) {
221+
if (bodyStreamCalled && !isBodyStreamRereadable()) {
222+
throw new RuntimeException("bodyStream() already called in read-once mode");
223+
}
224+
isBodyStreamRereadable = true;
225+
}
226+
}
227+
228+
/**
229+
* {@inheritDoc}
230+
*/
231+
@Override
232+
public void close() throws IOException {
233+
synchronized (this) {
234+
IOUtils.closeQuietly(bodyStream);
235+
isClosed = true;
236+
this.bodyBytes = null;
237+
}
238+
}
239+
119240
/**
120241
* Handles wrapping the body stream if indicated by the "Content-Encoding" header.
121242
*
@@ -155,13 +276,12 @@ public final int parseInt(String name) throws NumberFormatException {
155276

156277
/**
157278
* A ByteArrayResponse class
279+
*
280+
* @deprecated Inherit directly from {@link GitHubConnectorResponse}.
158281
*/
282+
@Deprecated
159283
public abstract static class ByteArrayResponse extends GitHubConnectorResponse {
160284

161-
private boolean inputStreamRead = false;
162-
private byte[] inputBytes = null;
163-
private boolean isClosed = false;
164-
165285
/**
166286
* Constructor for ByteArray Response
167287
*
@@ -177,52 +297,5 @@ protected ByteArrayResponse(@Nonnull GitHubConnectorRequest request,
177297
@Nonnull Map<String, List<String>> headers) {
178298
super(request, statusCode, headers);
179299
}
180-
181-
/**
182-
* {@inheritDoc}
183-
*/
184-
@Override
185-
@Nonnull
186-
public InputStream bodyStream() throws IOException {
187-
if (isClosed) {
188-
throw new IOException("Response is closed");
189-
}
190-
synchronized (this) {
191-
if (!inputStreamRead) {
192-
InputStream rawStream = rawBodyStream();
193-
try (InputStream stream = wrapStream(rawStream)) {
194-
if (stream != null) {
195-
inputBytes = IOUtils.toByteArray(stream);
196-
}
197-
}
198-
inputStreamRead = true;
199-
}
200-
}
201-
202-
if (inputBytes == null) {
203-
throw new IOException("Response body missing, stream null");
204-
}
205-
206-
return new ByteArrayInputStream(inputBytes);
207-
}
208-
209-
/**
210-
* Get the raw implementation specific body stream for this response.
211-
*
212-
* This method will only be called once to completion. If an exception is thrown, it may be called multiple
213-
* times.
214-
*
215-
* @return the stream for the raw response
216-
* @throws IOException
217-
* if an I/O Exception occurs.
218-
*/
219-
@CheckForNull
220-
protected abstract InputStream rawBodyStream() throws IOException;
221-
222-
@Override
223-
public void close() throws IOException {
224-
isClosed = true;
225-
this.inputBytes = null;
226-
}
227300
}
228301
}

src/main/java/org/kohsuke/github/extras/HttpClientGitHubConnector.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ public GitHubConnectorResponse send(GitHubConnectorRequest connectorRequest) thr
9393
*
9494
* Implementation specific to {@link HttpResponse}.
9595
*/
96-
private static class HttpClientGitHubConnectorResponse extends GitHubConnectorResponse.ByteArrayResponse {
96+
private static class HttpClientGitHubConnectorResponse extends GitHubConnectorResponse {
9797

9898
@Nonnull
9999
private final HttpResponse<InputStream> response;
@@ -113,7 +113,6 @@ protected InputStream rawBodyStream() throws IOException {
113113
@Override
114114
public void close() throws IOException {
115115
super.close();
116-
IOUtils.closeQuietly(response.body());
117116
}
118117
}
119118
}

src/main/java/org/kohsuke/github/extras/okhttp3/OkHttpGitHubConnector.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
import okhttp3.*;
44
import org.apache.commons.io.IOUtils;
5-
import org.kohsuke.github.*;
65
import org.kohsuke.github.connector.GitHubConnector;
76
import org.kohsuke.github.connector.GitHubConnectorRequest;
87
import org.kohsuke.github.connector.GitHubConnectorResponse;
@@ -104,7 +103,7 @@ private List<ConnectionSpec> TlsConnectionSpecs() {
104103
*
105104
* Implementation specific to {@link okhttp3.Response}.
106105
*/
107-
private static class OkHttpGitHubConnectorResponse extends GitHubConnectorResponse.ByteArrayResponse {
106+
private static class OkHttpGitHubConnectorResponse extends GitHubConnectorResponse {
108107

109108
@Nonnull
110109
private final Response response;

src/test/java/org/kohsuke/github/AbstractGitHubWireMockTest.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
import java.io.IOException;
2020
import java.util.*;
2121

22-
import static org.hamcrest.Matchers.*;
2322
import static org.junit.Assume.assumeFalse;
2423
import static org.junit.Assume.assumeTrue;
2524

src/test/java/org/kohsuke/github/GHPullRequestTest.java

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -918,9 +918,7 @@ public void getUserTest() throws IOException {
918918
prSingle.getMergeable();
919919
assertThat(prSingle.getUser().root(), notNullValue());
920920

921-
PagedIterable<GHPullRequest> ghPullRequests = getRepository().queryPullRequests()
922-
.state(GHIssueState.OPEN)
923-
.list();
921+
List<GHPullRequest> ghPullRequests = getRepository().getPullRequests(GHIssueState.OPEN);
924922
for (GHPullRequest pr : ghPullRequests) {
925923
assertThat(pr.getUser().root(), notNullValue());
926924
pr.getMergeable();

src/test/java/org/kohsuke/github/GHRateLimitTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,7 @@ public void testGitHubRateLimit() throws Exception {
188188
// Verify the requesting a search url updates the search rate limit
189189
assertThat(gitHub.lastRateLimit().getSearch().getRemaining(), equalTo(30));
190190

191-
HashMap<String, Object> searchResult = (HashMap<String, Object>) gitHub.createRequest()
191+
HashMap<String, Object> searchResult = gitHub.createRequest()
192192
.rateLimit(RateLimitTarget.SEARCH)
193193
.setRawUrlPath(mockGitHub.apiServer().baseUrl()
194194
+ "/search/repositories?q=tetris+language%3Aassembly&sort=stars&order=desc")

0 commit comments

Comments
 (0)