Skip to content

Commit fd67189

Browse files
committed
JIRA:GRIF-316 upgrade gooddata-http-client to JDK17
1 parent b2e91f1 commit fd67189

8 files changed

Lines changed: 134 additions & 77 deletions

File tree

NOTICE.txt

Lines changed: 21 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -8,45 +8,43 @@ This project includes a number of subcomponents with separate copyright notices
88
================================================================================
99
DEPENDENCIES
1010

11-
- Apache Commons Codec (1.11) [Apache-2.0, BSD-3-Clause]
1211
- Apache Commons Lang (3.12.0) [Apache-2.0]
13-
- Apache Log4j (1.2.17) [Apache-2.0]
14-
- Byte Buddy (without dependencies) (1.11.13) [BSD-3-Clause, Apache-2.0]
15-
- Byte Buddy agent (1.11.13) [Apache-2.0]
16-
- Commons Logging (1.2) [Apache-2.0]
12+
- Apache Commons Text (1.10.0) [Apache-2.0]
13+
- Apache HttpClient 5 (5.5) [Apache-2.0]
14+
- Apache HttpCore 5 (5.3.4) [Apache-2.0]
15+
- Apache HttpCore 5 H2 (5.3.4) [Apache-2.0]
16+
- Byte Buddy (without dependencies) (1.14.4) [Apache-2.0]
17+
- Byte Buddy agent (1.14.4) [Apache-2.0]
1718
- commons-collections (3.2.2) [Apache-2.0]
1819
- commons-io (2.11.0) [Apache-2.0]
20+
- commons-lang (2.6) [Apache-2.0]
1921
- Hamcrest (2.2) [BSD-3-Clause]
20-
- Hamcrest Core (1.3) [BSD-3-Clause]
21-
- Hamcrest library (1.3) BSD-3-Clause]
22-
- HttpClient (4.5.13) [Apache-2.0]
23-
- HttpCore (4.4.14) [Apache-2.0]
24-
- Jackson-annotations (2.13.0) [Apache-2.0]
25-
- Jackson-core (2.13.0) [Apache-2.0]
26-
- jackson-databind (2.13.0) [Apache-2.0]
27-
- jadler-all (1.3.0) [MIT]
28-
- jadler-core (1.3.0) [MIT]
29-
- jadler-jetty (1.3.0) [MIT]
30-
- jadler-junit (1.3.0) [MIT]
22+
- Jackson-annotations (2.14.2) [Apache-2.0]
23+
- Jackson-core (2.14.2) [Apache-2.0]
24+
- jackson-databind (2.14.2) [Apache-2.0]
25+
- jadler-core (1.3.1) [MIT]
26+
- jadler-jetty (1.3.1) [MIT]
3127
- Jetty :: Continuation (8.1.11.v20130520) [Multi-license: EPL-1.0 OR Apache-2.0]
3228
- Jetty :: Http Utility (8.1.11.v20130520) [Multi-license: EPL-1.0 OR Apache-2.0]
3329
- Jetty :: IO Utility (8.1.11.v20130520) [Multi-license: EPL-1.0 OR Apache-2.0]
3430
- Jetty :: Server Core (8.1.11.v20130520) [Multi-license: EPL-1.0 OR Apache-2.0]
3531
- Jetty :: Utilities (8.1.11.v20130520) [Multi-license: EPL-1.0 OR Apache-2.0, MIT]
3632
- Jetty Orbit :: Servlet API (3.0.0.v201112011016) [Multi-license: CDDL-1.0 OR GPL-2.0-with-classpath-exception, EPL-1.0, Apache-2.0]
37-
- JUnit (4.13.2) [CPL-1.0]
38-
- Lang (2.6) [Apache-2.0]
39-
- Mockito (3.12.4) [MIT, Apache-2.0]
40-
- Objenesis (3.2) [MIT]
41-
- SLF4J API Module (1.7.32) [MIT]
42-
- SLF4J LOG4J-12 Binding relocated (1.7.32) [MIT]
33+
- JUnit Jupiter API (5.10.2) [EPL-2.0]
34+
- JUnit Jupiter Engine (5.10.2) [EPL-2.0]
35+
- Mockito Core (5.3.1) [MIT]
36+
- Objenesis (3.3) [Apache-2.0]
37+
- SLF4J API Module (2.0.7) [MIT]
38+
- SLF4J Simple Binding (2.0.7) [MIT]
4339

4440
APPENDIX: LICENSES
4541
Apache-2.0
42+
BSD-3-Clause
4643
CDDL-1.0
47-
CPL-1.0
4844
EPL-1.0
45+
EPL-2.0
4946
GPL-2.0-with-classpath-exception
47+
MIT
5048
[End of Table of Contents]
5149

5250
================================================================================

pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
<parent>
1919
<groupId>com.gooddata</groupId>
2020
<artifactId>gooddata-parent</artifactId>
21-
<version>3.1.0</version>
21+
<version>4.0.3</version>
2222
</parent>
2323

2424
<developers>

src/main/java/com/gooddata/http/client/GoodDataHttpClient.java

Lines changed: 29 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,11 @@
1313
import org.apache.hc.client5.http.classic.HttpClient;
1414
import org.apache.hc.core5.http.ClassicHttpRequest;
1515
import org.apache.hc.core5.http.ClassicHttpResponse;
16+
import org.apache.hc.core5.http.ContentType;
1617
import org.apache.hc.core5.http.HttpHost;
1718
import org.apache.hc.core5.http.HttpStatus;
1819
import org.apache.hc.core5.http.io.HttpClientResponseHandler;
20+
import org.apache.hc.core5.http.io.entity.ByteArrayEntity;
1921
import org.apache.hc.core5.http.io.entity.EntityUtils;
2022
import org.apache.hc.core5.http.message.BasicClassicHttpResponse;
2123
import org.apache.hc.core5.http.protocol.HttpContext;
@@ -27,7 +29,6 @@
2729

2830
import java.io.IOException;
2931
import java.net.URI;
30-
import java.util.concurrent.TimeUnit;
3132
import java.util.concurrent.locks.Lock;
3233
import java.util.concurrent.locks.ReadWriteLock;
3334
import java.util.concurrent.locks.ReentrantLock;
@@ -141,7 +142,7 @@ private ClassicHttpResponse handleResponse(
141142
}
142143
}
143144
final ClassicHttpRequest retryRequest = cloneRequestWithNewTT(originalRequest, tt);
144-
return this.httpClient.execute(httpHost, retryRequest, context, response -> response);
145+
return this.httpClient.execute(httpHost, retryRequest, context, response -> copyResponseEntity(response));
145146
} else {
146147
tokenRefreshing = true;
147148
}
@@ -175,7 +176,7 @@ private ClassicHttpResponse handleResponse(
175176
}
176177

177178
final ClassicHttpRequest retryRequest = cloneRequestWithNewTT(originalRequest, tt);
178-
ClassicHttpResponse retryResponse = this.httpClient.execute(httpHost, retryRequest, context, response -> response);
179+
ClassicHttpResponse retryResponse = this.httpClient.execute(httpHost, retryRequest, context, response -> copyResponseEntity(response));
179180

180181

181182
if (retryResponse.getCode() == HttpStatus.SC_UNAUTHORIZED &&
@@ -309,12 +310,7 @@ public ClassicHttpResponse execute(HttpHost target, ClassicHttpRequest request,
309310
request.addHeader(TT_HEADER, tt);
310311
}
311312

312-
ClassicHttpResponse resp = this.httpClient.execute(
313-
target,
314-
request,
315-
context,
316-
response -> response
317-
);
313+
ClassicHttpResponse resp = this.httpClient.execute(target, request, context, response -> copyResponseEntity(response));
318314

319315
if (resp.getCode() == HttpStatus.SC_UNAUTHORIZED) {
320316
// 👇 Proper handling of InterruptedException
@@ -343,7 +339,7 @@ public ClassicHttpResponse execute(HttpHost target, ClassicHttpRequest request)
343339

344340

345341
public <T> T execute(HttpHost target, ClassicHttpRequest request, HttpContext context,
346-
HttpClientResponseHandler<? extends T> responseHandler) throws IOException {
342+
HttpClientResponseHandler<? extends T> responseHandler) throws IOException, org.apache.hc.core5.http.HttpException {
347343
return httpClient.execute(target, request, context, responseHandler);
348344
}
349345
/**
@@ -354,4 +350,27 @@ private boolean isLogoutRequest(HttpHost target, ClassicHttpRequest request) {
354350
&& "DELETE".equals(request.getMethod())
355351
&& URI.create(request.getRequestUri()).getPath().startsWith(LOGIN_URL);
356352
}
353+
354+
/**
355+
* Helper method to copy response entity to avoid stream closure issues.
356+
* Returns a new response with the same properties but a copied entity.
357+
*/
358+
private ClassicHttpResponse copyResponseEntity(ClassicHttpResponse response) throws IOException {
359+
if (response.getEntity() == null) {
360+
return response;
361+
}
362+
363+
// Copy the entity content
364+
byte[] content = EntityUtils.toByteArray(response.getEntity());
365+
ContentType contentType = ContentType.parseLenient(response.getEntity().getContentType());
366+
367+
// Create a new response with copied entity
368+
BasicClassicHttpResponse newResponse = new BasicClassicHttpResponse(response.getCode(), response.getReasonPhrase());
369+
for (Header header : response.getHeaders()) {
370+
newResponse.addHeader(header);
371+
}
372+
newResponse.setEntity(new ByteArrayEntity(content, contentType));
373+
374+
return newResponse;
375+
}
357376
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/*
2+
* (C) 2022 GoodData Corporation.
3+
* This source code is licensed under the BSD-style license found in the
4+
* LICENSE.txt file in the root directory of this source tree.
5+
*/
6+
package com.gooddata.http.client;
7+
8+
/**
9+
* Exception thrown when HTTP operations fail with specific status codes.
10+
* This exception wraps HTTP status information to provide detailed error context.
11+
*/
12+
public class GoodDataHttpStatusException extends RuntimeException {
13+
14+
private final int code;
15+
private final String reason;
16+
17+
public GoodDataHttpStatusException(int code, String reason) {
18+
super("HTTP " + code + ": " + reason);
19+
this.code = code;
20+
this.reason = reason;
21+
}
22+
23+
public GoodDataHttpStatusException(String message, int code, String reason) {
24+
super(message);
25+
this.code = code;
26+
this.reason = reason;
27+
}
28+
29+
public GoodDataHttpStatusException(String message, Throwable cause, int code, String reason) {
30+
super(message, cause);
31+
this.code = code;
32+
this.reason = reason;
33+
}
34+
35+
public int getCode() {
36+
return code;
37+
}
38+
39+
public String getReason() {
40+
return reason;
41+
}
42+
}

src/test/java/com/gooddata/http/client/GoodDataHttpClientAT.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,15 +29,15 @@ public class GoodDataHttpClientAT {
2929
private final HttpHost httpHost = new HttpHost("https", System.getProperty("GDC_BACKEND", "secure.gooddata.com"), 443);
3030

3131
@Test
32-
public void gdcLogin() throws IOException {
32+
public void gdcLogin() throws IOException, org.apache.hc.core5.http.HttpException {
3333
// Modern style: use TestUtils.createGoodDataClient, returns a GoodDataHttpClient wrapper
3434
final GoodDataHttpClient client = createGoodDataClient(login, password, httpHost);
3535
// performGet expects GoodDataHttpClient, uses execute() with a lambda (see TestUtils)
3636
performGet(client, httpHost, GDC_PROJECTS_PATH, HttpStatus.SC_OK);
3737
}
3838

3939
@Test
40-
public void gdcSstSimple() throws IOException {
40+
public void gdcSstSimple() throws IOException, org.apache.hc.core5.http.HttpException {
4141
final HttpClient httpClient = HttpClients.createDefault();
4242
final LoginSSTRetrievalStrategy loginSSTRetrievalStrategy = new LoginSSTRetrievalStrategy(login, password);
4343
final String sst = loginSSTRetrievalStrategy.obtainSst(httpClient, httpHost);
@@ -52,7 +52,7 @@ public void gdcSstSimple() throws IOException {
5252
private static final Pattern profilePattern = Pattern.compile("\"/gdc/account/profile/([^\"]+)\"");
5353

5454
@Test
55-
public void gdcLogout() throws IOException {
55+
public void gdcLogout() throws IOException, org.apache.hc.core5.http.HttpException {
5656
final GoodDataHttpClient client = createGoodDataClient(login, password, httpHost);
5757
final String response = getForEntity(client, httpHost, "/gdc/account/profile/current", HttpStatus.SC_OK);
5858
final Matcher matcher = profilePattern.matcher(response);

src/test/java/com/gooddata/http/client/GoodDataHttpClientIntegrationTest.java

Lines changed: 15 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ public void tearDown() {
8484
}
8585

8686
@Test
87-
public void vi () throws IOException {
87+
public void vi () throws IOException, org.apache.hc.core5.http.HttpException {
8888
mock401OnProjects();
8989
mock401OnToken();
9090

@@ -96,7 +96,11 @@ public void vi () throws IOException {
9696

9797
final GoodDataHttpClient client = createGoodDataClient(jadlerLogin, jadlerPassword, jadlerHost);
9898

99-
performGet(client, jadlerHost, GDC_PROJECTS_PATH, HttpStatus.SC_UNAUTHORIZED);
99+
// When login fails with 401, obtainSst() throws GoodDataAuthException
100+
org.junit.jupiter.api.Assertions.assertThrows(
101+
GoodDataAuthException.class,
102+
() -> performGet(client, jadlerHost, GDC_PROJECTS_PATH, HttpStatus.SC_UNAUTHORIZED)
103+
);
100104
}
101105

102106

@@ -150,7 +154,7 @@ private PerformGetWithCountDown(GoodDataHttpClient client, String path, CountDow
150154
public void run() {
151155
try {
152156
performGet(client, jadlerHost, path, HttpStatus.SC_OK);
153-
} catch (IOException e) {
157+
} catch (IOException | org.apache.hc.core5.http.HttpException e) {
154158
throw new IllegalStateException("Can't execute get", e);
155159
} finally {
156160
countDown.countDown();
@@ -159,7 +163,7 @@ public void run() {
159163
}
160164

161165
@Test
162-
public void redirect() throws IOException {
166+
public void redirect() throws IOException, org.apache.hc.core5.http.HttpException {
163167

164168
onRequest()
165169
.havingMethodEqualTo("GET")
@@ -196,7 +200,7 @@ public void redirect() throws IOException {
196200
}
197201

198202
@Test
199-
public void getProjectOkNoTtRefresh() throws IOException {
203+
public void getProjectOkNoTtRefresh() throws IOException, org.apache.hc.core5.http.HttpException {
200204
onRequest()
201205
.havingMethodEqualTo("GET")
202206
.havingPathEqualTo(REDIRECT_PATH)
@@ -216,44 +220,33 @@ public void getProjectOkNoTtRefresh() throws IOException {
216220
}
217221

218222
@Test
219-
public void shouldLogoutOk() throws IOException {
223+
public void shouldLogoutOk() throws IOException, org.apache.hc.core5.http.HttpException {
224+
// Setup mocks to trigger authentication, then test logout
220225
mock401OnProjects();
221226
mock200OnProjects();
222-
227+
223228
mock401OnToken();
224229
mock200OnToken();
225230

226231
mockLogin();
227232

228233
mockLogout("profileId");
229-
// Mock for the DELETE logout request
230-
// This ensures that a DELETE to /gdc/account/login/profileId returns HTTP 204 (No Content)
231234
onRequest()
232235
.havingMethodEqualTo("DELETE")
233236
.havingPathEqualTo("/gdc/account/login/profileId")
234237
.respond()
235238
.withStatus(204);
236-
// Mock for the GET request after redirect
237-
// This ensures GET on REDIRECT_PATH returns 200 with a valid JSON body
238-
onRequest()
239-
.havingMethodEqualTo("GET")
240-
.havingPathEqualTo(REDIRECT_PATH)
241-
.respond()
242-
.withStatus(200)
243-
.withBody(BODY_PROJECTS)
244-
.withEncoding(CHARSET)
245-
.withContentType(CONTENT_TYPE_JSON_UTF);
246239

247240
final GoodDataHttpClient client = createGoodDataClient(jadlerLogin, jadlerPassword, jadlerHost);
248241

249242
try {
250-
// Perform the GET request and expect HTTP 200 OK
251-
performGet(client, jadlerHost, REDIRECT_PATH, HttpStatus.SC_OK);
243+
// Request to GDC_PROJECTS_PATH will trigger 401, authenticate, then return 200
244+
performGet(client, jadlerHost, GDC_PROJECTS_PATH, HttpStatus.SC_OK);
252245
} catch (IOException e) {
253246
throw new RuntimeException("GET request failed", e);
254247
}
255248

256-
// Test the logout operation and expect HTTP 204 No Content
249+
// Now logout - SST and TT should be set from the authentication above
257250
logout(client, jadlerHost, "profileId", HttpStatus.SC_NO_CONTENT);
258251
}
259252

0 commit comments

Comments
 (0)