Skip to content

Commit 08f2777

Browse files
authored
fix: (odata-client) Move disable-buffer toggle from result object to request builder (#850)
1 parent d283f4c commit 08f2777

File tree

22 files changed

+347
-160
lines changed

22 files changed

+347
-160
lines changed

datamodel/odata-client/src/main/java/com/sap/cloud/sdk/datamodel/odata/client/request/ODataRequestBatch.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ public ODataRequestBatch(
9292
this.uuidProvider = uuidProvider;
9393
this.batchUuid = uuidProvider.get();
9494
this.headers.remove(HttpHeaders.ACCEPT); // batch request does not require Accept header
95+
this.requestResultFactory = ODataRequestResultFactory.WITHOUT_BUFFER;
9596
}
9697

9798
@Nonnull

datamodel/odata-client/src/main/java/com/sap/cloud/sdk/datamodel/odata/client/request/ODataRequestGeneric.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,12 @@ public abstract class ODataRequestGeneric implements ODataRequestExecutable
8989
@Setter
9090
protected CsrfTokenRetriever csrfTokenRetriever;
9191

92+
/**
93+
* The response buffer strategy to use for this request.
94+
*/
95+
@Nonnull
96+
ODataRequestResultFactory requestResultFactory = ODataRequestResultFactory.WITH_BUFFER;
97+
9298
ODataRequestGeneric(
9399
@Nonnull final String servicePath,
94100
@Nonnull final ODataResourcePath resourcePath,
@@ -229,7 +235,7 @@ public void addQueryParameter( @Nonnull final String key, @Nullable final String
229235
{
230236
return Try
231237
.ofSupplier(httpOperation)
232-
.map(response -> new ODataRequestResultGeneric(this, response, httpClient))
238+
.map(response -> requestResultFactory.create(this, response, httpClient))
233239
.andThenTry(ODataHealthyResponseValidator::requireHealthyResponse);
234240
}
235241

datamodel/odata-client/src/main/java/com/sap/cloud/sdk/datamodel/odata/client/request/ODataRequestRead.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
import org.apache.http.client.HttpClient;
99

10+
import com.google.common.annotations.Beta;
1011
import com.google.common.net.UrlEscapers;
1112
import com.sap.cloud.sdk.datamodel.odata.client.ODataProtocol;
1213
import com.sap.cloud.sdk.datamodel.odata.client.expression.ODataResourcePath;
@@ -134,4 +135,15 @@ public ODataRequestResultGeneric execute( @Nonnull final HttpClient httpClient )
134135
: tryExecuteWithCsrfToken(httpClient, request::requestGet);
135136
return result.get();
136137
}
138+
139+
/**
140+
* Disable pre-buffering of http response entity.
141+
*/
142+
@Beta
143+
@Nonnull
144+
public ODataRequestResultResource.Executable withoutResponseBuffering()
145+
{
146+
requestResultFactory = ODataRequestResultFactory.WITHOUT_BUFFER;
147+
return httpClient -> (ODataRequestResultResource) this.execute(httpClient);
148+
}
137149
}

datamodel/odata-client/src/main/java/com/sap/cloud/sdk/datamodel/odata/client/request/ODataRequestReadByKey.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
import org.apache.http.client.HttpClient;
99

10+
import com.google.common.annotations.Beta;
1011
import com.sap.cloud.sdk.datamodel.odata.client.ODataProtocol;
1112
import com.sap.cloud.sdk.datamodel.odata.client.expression.ODataResourcePath;
1213
import com.sap.cloud.sdk.datamodel.odata.client.query.StructuredQuery;
@@ -128,4 +129,17 @@ public ODataRequestResultGeneric execute( @Nonnull final HttpClient httpClient )
128129
: tryExecuteWithCsrfToken(httpClient, request::requestGet);
129130
return result.get();
130131
}
132+
133+
/**
134+
* Disable pre-buffering of http response entity.
135+
*
136+
* @since 5.21.0
137+
*/
138+
@Beta
139+
@Nonnull
140+
public ODataRequestResultResource.Executable withoutResponseBuffering()
141+
{
142+
requestResultFactory = ODataRequestResultFactory.WITHOUT_BUFFER;
143+
return httpClient -> (ODataRequestResultResource) this.execute(httpClient);
144+
}
131145
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
package com.sap.cloud.sdk.datamodel.odata.client.request;
2+
3+
import static org.slf4j.LoggerFactory.getLogger;
4+
5+
import javax.annotation.Nonnull;
6+
import javax.annotation.Nullable;
7+
8+
import org.apache.http.HttpResponse;
9+
import org.apache.http.StatusLine;
10+
import org.apache.http.client.HttpClient;
11+
import org.apache.http.entity.BufferedHttpEntity;
12+
import org.apache.http.message.BasicHttpResponse;
13+
import org.slf4j.Logger;
14+
15+
import io.vavr.control.Option;
16+
import io.vavr.control.Try;
17+
18+
/**
19+
* Enum representing the strategy for buffering HTTP responses.
20+
*/
21+
@FunctionalInterface
22+
interface ODataRequestResultFactory
23+
{
24+
/**
25+
* Strategy that does not buffer the response.
26+
*/
27+
ODataRequestResultFactory WITHOUT_BUFFER = ODataRequestResultResource::new;
28+
29+
/**
30+
* Strategy that buffers the response by creating a copy of it.
31+
*/
32+
ODataRequestResultFactory WITH_BUFFER = ( oDataRequest, httpResponse, httpClient ) -> {
33+
final StatusLine status = httpResponse.getStatusLine();
34+
final BasicHttpResponse copy = new BasicHttpResponse(status);
35+
Option.of(httpResponse.getLocale()).peek(copy::setLocale);
36+
Option.of(httpResponse.getAllHeaders()).peek(copy::setHeaders);
37+
38+
final Logger log = getLogger(ODataRequestResultFactory.class);
39+
Option
40+
.of(httpResponse.getEntity())
41+
.onEmpty(() -> log.debug("HTTP response entity is empty: {}", status))
42+
.map(entity -> Try.run(() -> copy.setEntity(new BufferedHttpEntity(entity))))
43+
.peek(b -> b.onSuccess(v -> log.debug("Successfully buffered the HTTP response entity.")))
44+
.peek(b -> b.onFailure(e -> log.warn("Failed to buffer HTTP response entity: {}", status, e)));
45+
46+
return new ODataRequestResultGeneric(oDataRequest, copy, httpClient);
47+
};
48+
49+
ODataRequestResultGeneric create(
50+
@Nonnull final ODataRequestGeneric oDataRequest,
51+
@Nonnull final HttpResponse httpResponse,
52+
@Nullable final HttpClient httpClient );
53+
}

datamodel/odata-client/src/main/java/com/sap/cloud/sdk/datamodel/odata/client/request/ODataRequestResultGeneric.java

Lines changed: 4 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,7 @@
2525
import org.apache.http.HttpResponse;
2626
import org.apache.http.StatusLine;
2727
import org.apache.http.client.HttpClient;
28-
import org.apache.http.entity.BufferedHttpEntity;
2928
import org.apache.http.entity.ContentType;
30-
import org.apache.http.message.BasicHttpResponse;
3129
import org.apache.http.util.EntityUtils;
3230

3331
import com.google.common.base.Strings;
@@ -70,15 +68,12 @@ public class ODataRequestResultGeneric
7068
{
7169
private final ODataResponseDeserializer deserializer;
7270

73-
@Nullable
74-
private volatile HttpResponse bufferedHttpResponse;
75-
private volatile boolean isBufferHttpResponse = true;
76-
7771
@Getter
7872
@Nonnull
7973
private final ODataRequestGeneric oDataRequest;
8074

8175
@Nonnull
76+
@Getter
8277
private final HttpResponse httpResponse;
8378

8479
private NumberDeserializationStrategy numberStrategy = NumberDeserializationStrategy.DOUBLE;
@@ -155,63 +150,12 @@ public ODataRequestResultGeneric withNumberDeserializationStrategy(
155150
* Method that allows consumers to disable buffering HTTP response entity. Note that once this is disabled, HTTP
156151
* responses can only be streamed/read once
157152
*
153+
* @deprecated Please use {@link ODataRequestRead#withoutResponseBuffering()} or
154+
* {@link ODataRequestReadByKey#withoutResponseBuffering()} instead.
158155
*/
156+
@Deprecated
159157
public void disableBufferingHttpResponse()
160158
{
161-
if( bufferedHttpResponse == null ) {
162-
isBufferHttpResponse = false;
163-
} else {
164-
log.warn("Buffering the HTTP response cannot be disabled! The content has already been buffered.");
165-
}
166-
}
167-
168-
/**
169-
* Method that creates a {@link BufferedHttpEntity} from the {@link HttpEntity} if buffering the HTTP response is
170-
* not turned off by using {@link ODataRequestResultGeneric#disableBufferingHttpResponse()}.
171-
*
172-
* @return An HttpResponse
173-
*/
174-
@Nonnull
175-
@Override
176-
public HttpResponse getHttpResponse()
177-
{
178-
if( !isBufferHttpResponse ) {
179-
log.debug("Buffering is disabled, returning unbuffered http response");
180-
return httpResponse;
181-
}
182-
183-
if( bufferedHttpResponse != null ) {
184-
return Objects.requireNonNull(bufferedHttpResponse);
185-
}
186-
187-
synchronized( this ) {
188-
if( bufferedHttpResponse != null ) {
189-
return Objects.requireNonNull(bufferedHttpResponse);
190-
}
191-
192-
final StatusLine statusLine = httpResponse.getStatusLine();
193-
final HttpEntity httpEntity = httpResponse.getEntity();
194-
if( statusLine == null || httpEntity == null ) {
195-
log
196-
.debug(
197-
"skipping buffering of http entity as either there is no http entity or response does not include a status-line.");
198-
return httpResponse;
199-
}
200-
201-
final Try<HttpEntity> entity = Try.of(() -> new BufferedHttpEntity(httpEntity));
202-
if( entity.isFailure() ) {
203-
log.warn("Failed to buffer HTTP response. Unable to buffer HTTP entity.", entity.getCause());
204-
return httpResponse;
205-
}
206-
207-
final BasicHttpResponse proxyResponse = new BasicHttpResponse(statusLine);
208-
proxyResponse.setHeaders(httpResponse.getAllHeaders());
209-
proxyResponse.setEntity(entity.get());
210-
Option.of(httpResponse.getLocale()).peek(proxyResponse::setLocale);
211-
bufferedHttpResponse = proxyResponse;
212-
}
213-
214-
return Objects.requireNonNull(bufferedHttpResponse);
215159
}
216160

217161
@Override

datamodel/odata-client/src/main/java/com/sap/cloud/sdk/datamodel/odata/client/request/ODataRequestResultMultipartGeneric.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,6 @@ public ODataRequestResultGeneric getResult( @Nonnull final ODataRequestGeneric r
125125
}
126126

127127
final ODataRequestResultGeneric result = new ODataRequestResultGeneric(batchRequest, response);
128-
result.disableBufferingHttpResponse(); // the artificial HttpResponse is static, no buffer required
129128
ODataHealthyResponseValidator.requireHealthyResponse(result);
130129
return result;
131130
}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
package com.sap.cloud.sdk.datamodel.odata.client.request;
2+
3+
import java.io.IOException;
4+
5+
import javax.annotation.Nonnull;
6+
import javax.annotation.Nullable;
7+
8+
import org.apache.http.HttpResponse;
9+
import org.apache.http.client.HttpClient;
10+
import org.apache.http.util.EntityUtils;
11+
12+
import com.google.common.annotations.Beta;
13+
14+
import lombok.EqualsAndHashCode;
15+
import lombok.extern.slf4j.Slf4j;
16+
17+
/**
18+
* OData request result for reading entities. The request is not yet closed. The response is not consumed. The
19+
* connection is not yet released.
20+
* <p>
21+
* <b>Note:</b> This class implements {@link AutoCloseable} and should be closed after use to ensure that the underlying
22+
* connection is released.
23+
* <p>
24+
* <b>Note:</b> This class is not thread-safe. The HTTP response object should not be consumed by multiple threads at
25+
* the same time.
26+
*
27+
* @since 5.21.0
28+
*/
29+
@Slf4j
30+
@Beta
31+
@EqualsAndHashCode( callSuper = true )
32+
public class ODataRequestResultResource extends ODataRequestResultGeneric implements AutoCloseable
33+
{
34+
/**
35+
* Default constructor.
36+
*
37+
* @param oDataRequest
38+
* The original OData request
39+
* @param httpResponse
40+
* The original Http response
41+
* @param httpClient
42+
* The Http client used to execute the request
43+
*/
44+
ODataRequestResultResource(
45+
@Nonnull final ODataRequestGeneric oDataRequest,
46+
@Nonnull final HttpResponse httpResponse,
47+
@Nullable final HttpClient httpClient )
48+
{
49+
super(oDataRequest, httpResponse, httpClient);
50+
}
51+
52+
@Override
53+
public void close()
54+
{
55+
try {
56+
EntityUtils.consume(getHttpResponse().getEntity());
57+
}
58+
catch( final IOException e ) {
59+
log.warn("Failed to close the HTTP response entity.", e);
60+
}
61+
}
62+
63+
/**
64+
* Interface for executing OData requests that return a resource which must be closed.
65+
*/
66+
@FunctionalInterface
67+
public interface Executable extends ODataRequestExecutable
68+
{
69+
@Nonnull
70+
@Override
71+
ODataRequestResultResource execute( @Nonnull final HttpClient httpClient );
72+
}
73+
}

0 commit comments

Comments
 (0)