66package com .gooddata .sdk .common ;
77
88
9- import org .apache .http .HttpEntityEnclosingRequest ;
10- import org .apache .http .HttpRequest ;
11- import org .apache .http .client .HttpClient ;
12- import org .apache .http .client .methods .*;
13- import org .apache .http .entity .ByteArrayEntity ;
9+ import org .apache .hc .core5 .http .ClassicHttpRequest ;
10+ import org .apache .hc .core5 .http .io .entity .ByteArrayEntity ;
11+ import org .apache .hc .core5 .http .io .support .ClassicRequestBuilder ;
12+ import org .apache .hc .client5 .http .classic .HttpClient ;
1413import org .slf4j .Logger ;
1514import org .slf4j .LoggerFactory ;
1615
2928import java .util .Map ;
3029
3130/**
32- * Spring 6 compatible {@link ClientHttpRequestFactory} implementation that uses Apache HttpComponents HttpClient 4.x.
33- * This is a custom implementation to bridge the gap between Spring 6 (which expects HttpClient 5.x)
34- * and our requirement to use HttpClient 4.x for compatibility.
31+ * Spring 6 compatible {@link ClientHttpRequestFactory} implementation that uses Apache HttpComponents HttpClient 5.x.
32+ * This is a custom implementation to bridge the gap between Spring 6 and HttpClient 5.x.
3533 */
3634public class HttpClient4ComponentsClientHttpRequestFactory implements ClientHttpRequestFactory {
3735
3836 private static final Logger logger = LoggerFactory .getLogger (HttpClient4ComponentsClientHttpRequestFactory .class );
3937 private final HttpClient httpClient ;
4038
4139 /**
42- * Create a factory with the given HttpClient 4 .x instance.
40+ * Create a factory with the given HttpClient 5 .x instance.
4341 *
44- * @param httpClient the HttpClient 4 .x instance to use
42+ * @param httpClient the HttpClient 5 .x instance to use
4543 */
4644 public HttpClient4ComponentsClientHttpRequestFactory (HttpClient httpClient ) {
4745 Assert .notNull (httpClient , "HttpClient must not be null" );
@@ -50,50 +48,50 @@ public HttpClient4ComponentsClientHttpRequestFactory(HttpClient httpClient) {
5048
5149 @ Override
5250 public ClientHttpRequest createRequest (URI uri , HttpMethod httpMethod ) throws IOException {
53- HttpUriRequest httpRequest = createHttpUriRequest (httpMethod , uri );
51+ ClassicHttpRequest httpRequest = createHttpUriRequest (httpMethod , uri );
5452 return new HttpClient4ComponentsClientHttpRequest (httpClient , httpRequest );
5553 }
5654
5755 /**
58- * Create an Apache HttpComponents HttpUriRequest object for the given HTTP method and URI.
56+ * Create an Apache HttpComponents ClassicHttpRequest object for the given HTTP method and URI.
5957 *
6058 * @param httpMethod the HTTP method
6159 * @param uri the URI
62- * @return the HttpUriRequest
60+ * @return the ClassicHttpRequest
6361 */
64- private HttpUriRequest createHttpUriRequest (HttpMethod httpMethod , URI uri ) {
62+ private ClassicHttpRequest createHttpUriRequest (HttpMethod httpMethod , URI uri ) {
6563 if (HttpMethod .GET .equals (httpMethod )) {
66- return new HttpGet (uri );
64+ return ClassicRequestBuilder . get (uri ). build ( );
6765 } else if (HttpMethod .HEAD .equals (httpMethod )) {
68- return new HttpHead (uri );
66+ return ClassicRequestBuilder . head (uri ). build ( );
6967 } else if (HttpMethod .POST .equals (httpMethod )) {
70- return new HttpPost (uri );
68+ return ClassicRequestBuilder . post (uri ). build ( );
7169 } else if (HttpMethod .PUT .equals (httpMethod )) {
72- return new HttpPut (uri );
70+ return ClassicRequestBuilder . put (uri ). build ( );
7371 } else if (HttpMethod .PATCH .equals (httpMethod )) {
74- return new HttpPatch (uri );
72+ return ClassicRequestBuilder . patch (uri ). build ( );
7573 } else if (HttpMethod .DELETE .equals (httpMethod )) {
76- return new HttpDelete (uri );
74+ return ClassicRequestBuilder . delete (uri ). build ( );
7775 } else if (HttpMethod .OPTIONS .equals (httpMethod )) {
78- return new HttpOptions (uri );
76+ return ClassicRequestBuilder . options (uri ). build ( );
7977 } else if (HttpMethod .TRACE .equals (httpMethod )) {
80- return new HttpTrace (uri );
78+ return ClassicRequestBuilder . trace (uri ). build ( );
8179 } else {
8280 throw new IllegalArgumentException ("Invalid HTTP method: " + httpMethod );
8381 }
8482 }
8583
8684 /**
87- * {@link ClientHttpRequest} implementation based on Apache HttpComponents HttpClient 4 .x.
85+ * {@link ClientHttpRequest} implementation based on Apache HttpComponents HttpClient 5 .x.
8886 */
8987 private static class HttpClient4ComponentsClientHttpRequest implements ClientHttpRequest {
9088
9189 private final HttpClient httpClient ;
92- private final HttpUriRequest httpRequest ;
90+ private final ClassicHttpRequest httpRequest ;
9391 private final HttpHeaders headers ;
9492 private ByteArrayOutputStream bufferedOutput = new ByteArrayOutputStream (1024 );
9593
96- public HttpClient4ComponentsClientHttpRequest (HttpClient httpClient , HttpUriRequest httpRequest ) {
94+ public HttpClient4ComponentsClientHttpRequest (HttpClient httpClient , ClassicHttpRequest httpRequest ) {
9795 this .httpClient = httpClient ;
9896 this .httpRequest = httpRequest ;
9997 this .headers = new HttpHeaders ();
@@ -111,7 +109,11 @@ public String getMethodValue() {
111109
112110 @ Override
113111 public URI getURI () {
114- return httpRequest .getURI ();
112+ try {
113+ return httpRequest .getUri ();
114+ } catch (Exception e ) {
115+ throw new RuntimeException ("Failed to get URI" , e );
116+ }
115117 }
116118
117119 @ Override
@@ -129,84 +131,35 @@ public ClientHttpResponse execute() throws IOException {
129131 // Create entity first (matching reference implementation exactly)
130132 byte [] bytes = bufferedOutput .toByteArray ();
131133 if (bytes .length > 0 ) {
132- if (httpRequest instanceof HttpEntityEnclosingRequest ) {
133- HttpEntityEnclosingRequest entityRequest = (HttpEntityEnclosingRequest ) httpRequest ;
134-
135- // Ensure proper UTF-8 encoding before creating entity
136- // This is crucial for @JsonTypeInfo annotated classes like Execution
137- ByteArrayEntity requestEntity = new ByteArrayEntity (bytes );
138-
139-
140- if (logger .isDebugEnabled ()) {
141- // Check if Content-Type is already set in headers
142- boolean hasContentType = false ;
143- for (org .apache .http .Header header : httpRequest .getAllHeaders ()) {
144- if ("Content-Type" .equalsIgnoreCase (header .getName ())) {
145- hasContentType = true ;
146- // String contentType = header.getValue();
147- // logger.debug("Content-Type from headers: {}", contentType);
148- break ;
149- }
150- }
151-
152- if (!hasContentType ) {
153- // logger.debug("Default Content-Type set: application/json; charset=UTF-8");
154- }
155- }
156-
157- entityRequest .setEntity (requestEntity );
158-
159- }
134+ // HttpClient 5.x - set entity directly on the request
135+ ByteArrayEntity requestEntity = new ByteArrayEntity (bytes , null );
136+ httpRequest .setEntity (requestEntity );
160137 }
161138
162139 // Set headers exactly like reference implementation
163- // (no additional headers parameter in our case, but same logic)
164140 addHeaders (httpRequest );
165141
166- // Handle both GoodDataHttpClient and standard HttpClient
167- org .apache .http .HttpResponse httpResponse ;
168- if (httpClient .getClass ().getName ().contains ("GoodDataHttpClient" )) {
169- // Use reflection to call the execute method on GoodDataHttpClient
170- try {
171- // Try the single parameter execute method first
172- java .lang .reflect .Method executeMethod = httpClient .getClass ().getMethod ("execute" ,
173- org .apache .http .client .methods .HttpUriRequest .class );
174- httpResponse = (org .apache .http .HttpResponse ) executeMethod .invoke (httpClient , httpRequest );
175- } catch (NoSuchMethodException e ) {
176- // If that doesn't work, try the two parameter version with HttpContext
177- try {
178- java .lang .reflect .Method executeMethod = httpClient .getClass ().getMethod ("execute" ,
179- org .apache .http .client .methods .HttpUriRequest .class , org .apache .http .protocol .HttpContext .class );
180- httpResponse = (org .apache .http .HttpResponse ) executeMethod .invoke (httpClient , httpRequest , null );
181- } catch (Exception e2 ) {
182- throw new IOException ("Failed to execute request with GoodDataHttpClient" , e2 );
183- }
184- } catch (Exception e ) {
185- throw new IOException ("Failed to execute request with GoodDataHttpClient" , e );
186- }
187- } else {
188- httpResponse = httpClient .execute (httpRequest );
189- }
190- return new HttpClient4ComponentsClientHttpResponse (httpResponse );
142+ // Execute the request using HttpClient 5.x API
143+ // The execute method with ResponseHandler automatically handles the response
144+ return httpClient .execute (httpRequest , response -> {
145+ // We need to consume and store the response since ResponseHandler closes it
146+ return new HttpClient4ComponentsClientHttpResponse (response );
147+ });
191148 }
192149
193150 /**
194151 * Add the headers from the HttpHeaders to the HttpRequest.
195- * Excludes Content-Length headers to avoid conflicts with HttpClient 4 .x internal management.
152+ * Excludes Content-Length headers to avoid conflicts with HttpClient 5 .x internal management.
196153 * Uses setHeader instead of addHeader to match the reference implementation.
197- * Follows HttpClient4ClientHttpRequest.executeInternal implementation pattern.
198154 */
199- private void addHeaders (HttpRequest httpRequest ) {
155+ private void addHeaders (ClassicHttpRequest httpRequest ) {
200156 // CRITICAL for GoodData API: set headers in fixed order
201157 // for stable checksum. Order: Accept, X-GDC-Version, Content-Type, others
202158
203159 // First clear potentially problematic headers
204- if (httpRequest instanceof HttpUriRequest ) {
205- HttpUriRequest uriRequest = (HttpUriRequest ) httpRequest ;
206- uriRequest .removeHeaders ("Accept" );
207- uriRequest .removeHeaders ("X-GDC-Version" );
208- uriRequest .removeHeaders ("Content-Type" );
209- }
160+ httpRequest .removeHeaders ("Accept" );
161+ httpRequest .removeHeaders ("X-GDC-Version" );
162+ httpRequest .removeHeaders ("Content-Type" );
210163
211164 // 1. Accept header (first for checksum stability)
212165 if (headers .containsKey ("Accept" )) {
@@ -243,8 +196,9 @@ private void addHeaders(HttpRequest httpRequest) {
243196 // logger.debug("Using Spring Content-Type: {}", finalContentType);
244197 // }
245198 }
246- } else if ( httpRequest instanceof HttpEntityEnclosingRequest ) {
199+ } else {
247200 // Set default Content-Type for JSON requests with body
201+ // In HttpClient 5.x, all requests can have entities, no need for instanceof check
248202 finalContentType = "application/json; charset=UTF-8" ;
249203 // if (logger.isDebugEnabled()) {
250204 // logger.debug("Default Content-Type for JSON requests: {}", finalContentType);
0 commit comments