Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

import java.io.IOException;
import java.net.URI;
import java.time.Duration;
import java.time.Instant;
import java.util.function.Function;

import javax.annotation.Nonnull;
Expand Down Expand Up @@ -104,11 +106,15 @@ private ClassicHttpResponse requestResource( @Nonnull final Function<URI, HttpUr

odataRequest.getListeners().forEach(v -> v.listenOnRequest(httpRequest));

final Instant start = Instant.now();
try {
return httpClient.executeOpen(null, httpRequest, null);
final ClassicHttpResponse response = httpClient.executeOpen(null, httpRequest, null);
odataRequest.getListeners().forEach(v -> v.listenOnResponse(response));
return response;
}
catch( final ClientProtocolException e ) {
log.debug("Connection could not be established.", e);
odataRequest.getListeners().forEach(v -> v.listenOnRequestError(e));
throw new ODataConnectionException(
this.odataRequest,
httpRequest,
Expand All @@ -117,6 +123,7 @@ private ClassicHttpResponse requestResource( @Nonnull final Function<URI, HttpUr
}
catch( final ConnectionRequestTimeoutException e ) {
log.debug("Connection pool timed out.", e);
odataRequest.getListeners().forEach(v -> v.listenOnRequestError(e));
throw new ODataConnectionException(this.odataRequest, httpRequest, """
Time out occurred because of a probable connection leak. Please execute your request \
with try-with-resources to ensure resources are properly closed. \
Expand All @@ -127,12 +134,18 @@ private ClassicHttpResponse requestResource( @Nonnull final Function<URI, HttpUr
}
catch( final IOException e ) {
log.debug("Connection was aborted.", e);
odataRequest.getListeners().forEach(v -> v.listenOnRequestError(e));
throw new ODataConnectionException(this.odataRequest, httpRequest, "Connection was aborted.", e);
}
catch( final Exception e ) {
log.debug("Connection failed.", e);
odataRequest.getListeners().forEach(v -> v.listenOnRequestError(e));
throw new ODataConnectionException(this.odataRequest, httpRequest, "Connection failed.", e);
}
finally {
final Duration duration = Duration.between(start, Instant.now());
odataRequest.getListeners().forEach(v -> v.listenOnExecutionFinished(duration));
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,6 @@ public abstract class ODataRequestGeneric implements ODataRequestExecutable
/**
* List of listeners to observe and react on OData actions.
*/
@Getter( AccessLevel.PROTECTED )
private final List<ODataRequestListener> listeners = new ArrayList<>();

/**
Expand Down Expand Up @@ -96,6 +95,18 @@ public abstract class ODataRequestGeneric implements ODataRequestExecutable
headers.putIfAbsent(HttpHeaders.ACCEPT, Lists.newArrayList(DEFAULT_FORMAT.getHttpAccept()));
}

/**
* Get the list of listeners to observe and react on OData actions.
*
* @return The list of listeners.
* @since 5.35.0
*/
@Nonnull
public List<ODataRequestListener> getListeners()
{
return listeners;
}

/**
* Get the static request URI of the OData resource.
*
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
package com.sap.cloud.sdk.datamodel.odata.client.request;

import java.time.Duration;

import javax.annotation.Nonnull;

import org.apache.hc.client5.http.classic.methods.HttpUriRequestBase;
import org.apache.hc.core5.http.ClassicHttpResponse;

/**
* Consumer class for the Listener Pattern to monitor and react on OData actions.
Expand All @@ -19,6 +22,28 @@ public interface ODataRequestListener
*/
void listenOnRequest( @Nonnull final HttpUriRequestBase request );

/**
* Handler to react after execution of an HTTP request, when the response is received.
*
* @param response
* The HTTP response.
* @since 5.35.0
*/
default void listenOnResponse( @Nonnull final ClassicHttpResponse response )
{
}

/**
* Handler to react after the request execution has finished (either successfully or with an error).
*
* @param duration
* The duration of the request execution.
* @since 5.35.0
*/
default void listenOnExecutionFinished( @Nonnull final Duration duration )
{
}

/**
* Handler to react on an error during request generation.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,12 @@ interface ODataRequestResultFactory
.onEmpty(() -> log.debug("HTTP response entity is empty: {}", status))
.map(entity -> Try.run(() -> copy.setEntity(new BufferedHttpEntity(entity))))
.peek(b -> b.onSuccess(v -> log.debug("Successfully buffered the HTTP response entity.")))
.peek(b -> b.onFailure(e -> log.warn("Failed to buffer HTTP response entity: {}", status, e)));
.peek(b -> b.onFailure(t -> {
log.warn("Failed to buffer HTTP response entity: {}", status, t);
if( t instanceof Exception ) {
oDataRequest.getListeners().forEach(l -> l.listenOnParsingError((Exception) t));
}
}));

Try.run(httpResponse::close).onFailure(e -> log.warn("Failed to close HTTP response: {}", status, e));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -165,21 +165,27 @@ public void streamElements( @Nonnull final Consumer<ResultElement> handler )
{
final GsonResultElementFactory resultElementFactory = getResultElementFactory();

final Integer numConsumedElements = HttpEntityReader.stream(this, reader -> {
deserializer.positionReaderToResultSet(reader);

int count = 0;
while( reader.hasNext() && reader.peek() == JsonToken.BEGIN_OBJECT ) {
final JsonElement jsonElement = JsonParser.parseReader(reader);
final ResultElement resultElement = resultElementFactory.create(jsonElement);
handler.accept(resultElement);
count++;
}
reader.close();
return count;
});

log.debug("Iterated {} elements.", numConsumedElements);
try {
final Integer numConsumedElements = HttpEntityReader.stream(this, reader -> {
deserializer.positionReaderToResultSet(reader);

int count = 0;
while( reader.hasNext() && reader.peek() == JsonToken.BEGIN_OBJECT ) {
final JsonElement jsonElement = JsonParser.parseReader(reader);
final ResultElement resultElement = resultElementFactory.create(jsonElement);
handler.accept(resultElement);
count++;
}
reader.close();
return count;
});

log.debug("Iterated {} elements.", numConsumedElements);
}
catch( final Exception e ) {
oDataRequest.getListeners().forEach(l -> l.listenOnParsingError(e));
throw e;
}
}

private GsonResultElementFactory getResultElementFactory()
Expand All @@ -204,22 +210,32 @@ private ResultPrimitive loadPrimitiveFromResponse(
@Nonnull final Function<JsonElement, JsonElement> jsonElementExtractor )
{
final GsonResultElementFactory elementFactory = getResultElementFactory();
final ResultPrimitive result = HttpEntityReader.read(this, element -> {
final Option<ResultPrimitive> single =
deserializer
.getElementToResultPrimitiveSingle(element)
.map(jsonElementExtractor)
.map(elementFactory::create)
.map(ResultElement::getAsPrimitive);
return single.getOrNull();
});
final ResultPrimitive result;
try {
result = HttpEntityReader.read(this, element -> {
final Option<ResultPrimitive> single =
deserializer
.getElementToResultPrimitiveSingle(element)
.map(jsonElementExtractor)
.map(elementFactory::create)
.map(ResultElement::getAsPrimitive);
return single.getOrNull();
});
}
catch( final Exception e ) {
oDataRequest.getListeners().forEach(l -> l.listenOnParsingError(e));
throw e;
}
if( result == null ) {
log.debug("{} response cannot be read as a primitive value.", protocol);
throw new ODataDeserializationException(
getODataRequest(),
getHttpResponse(),
"Unable to read " + protocol + " response.",
null);
final ODataDeserializationException e =
new ODataDeserializationException(
getODataRequest(),
getHttpResponse(),
"Unable to read " + protocol + " response.",
null);
oDataRequest.getListeners().forEach(l -> l.listenOnParsingError(e));
throw e;
}
return result;
}
Expand All @@ -229,21 +245,31 @@ private ResultCollection loadPrimitiveCollectionFromResponse()
{
final GsonResultElementFactory elementFactory = getResultElementFactory();

final ResultCollection result = HttpEntityReader.read(this, element -> {
final Option<ResultCollection> set =
deserializer
.getElementToResultPrimitiveSet(element)
.map(elementFactory::create)
.map(ResultElement::getAsCollection);
return set.getOrNull();
});
final ResultCollection result;
try {
result = HttpEntityReader.read(this, element -> {
final Option<ResultCollection> set =
deserializer
.getElementToResultPrimitiveSet(element)
.map(elementFactory::create)
.map(ResultElement::getAsCollection);
return set.getOrNull();
});
}
catch( final Exception e ) {
oDataRequest.getListeners().forEach(l -> l.listenOnParsingError(e));
throw e;
}
if( result == null ) {
log.debug("{} response cannot be read as set of primitive values.", protocol);
throw new ODataDeserializationException(
getODataRequest(),
getHttpResponse(),
"Unable to read " + protocol + " response.",
null);
final ODataDeserializationException e =
new ODataDeserializationException(
getODataRequest(),
getHttpResponse(),
"Unable to read " + protocol + " response.",
null);
oDataRequest.getListeners().forEach(l -> l.listenOnParsingError(e));
throw e;
}
return result;
}
Expand All @@ -252,22 +278,32 @@ private ResultCollection loadPrimitiveCollectionFromResponse()
private ResultObject loadEntryFromResponse( @Nonnull final Function<JsonElement, JsonElement> jsonElementExtractor )
{
final GsonResultElementFactory elementFactory = getResultElementFactory();
final ResultObject result = HttpEntityReader.read(this, element -> {
final Option<ResultObject> single =
deserializer
.getElementToResultSingle(element)
.map(jsonElementExtractor)
.map(elementFactory::create)
.map(ResultElement::getAsObject);
return single.getOrNull();
});
final ResultObject result;
try {
result = HttpEntityReader.read(this, element -> {
final Option<ResultObject> single =
deserializer
.getElementToResultSingle(element)
.map(jsonElementExtractor)
.map(elementFactory::create)
.map(ResultElement::getAsObject);
return single.getOrNull();
});
}
catch( final Exception e ) {
oDataRequest.getListeners().forEach(l -> l.listenOnParsingError(e));
throw e;
}
if( result == null ) {
log.debug("{} response cannot be read as a single entity.", protocol);
throw new ODataDeserializationException(
getODataRequest(),
getHttpResponse(),
"Unable to read " + protocol + " response.",
null);
final ODataDeserializationException e =
new ODataDeserializationException(
getODataRequest(),
getHttpResponse(),
"Unable to read " + protocol + " response.",
null);
oDataRequest.getListeners().forEach(l -> l.listenOnParsingError(e));
throw e;
}
return result;
}
Expand All @@ -277,21 +313,31 @@ private ResultCollection loadEntryCollectionFromResponse()
{
final GsonResultElementFactory elementFactory = getResultElementFactory();

final ResultCollection result = HttpEntityReader.read(this, element -> {
final Option<ResultCollection> set =
deserializer
.getElementToResultSet(element)
.map(elementFactory::create)
.map(ResultElement::getAsCollection);
return set.getOrNull();
});
final ResultCollection result;
try {
result = HttpEntityReader.read(this, element -> {
final Option<ResultCollection> set =
deserializer
.getElementToResultSet(element)
.map(elementFactory::create)
.map(ResultElement::getAsCollection);
return set.getOrNull();
});
}
catch( final Exception e ) {
oDataRequest.getListeners().forEach(l -> l.listenOnParsingError(e));
throw e;
}
if( result == null ) {
log.debug("{} response cannot be read as set of entities.", protocol);
throw new ODataDeserializationException(
getODataRequest(),
getHttpResponse(),
"Unable to read " + protocol + " response.",
null);
final ODataDeserializationException e =
new ODataDeserializationException(
getODataRequest(),
getHttpResponse(),
"Unable to read " + protocol + " response.",
null);
oDataRequest.getListeners().forEach(l -> l.listenOnParsingError(e));
throw e;
}
return result;
}
Expand Down Expand Up @@ -690,11 +736,14 @@ public boolean hasPayload()
private void assertNonEmptyPayload()
{
if( !hasPayload() ) {
throw new ODataDeserializationException(
getODataRequest(),
getHttpResponse(),
protocol + " response did not contain any payload.",
null);
final ODataDeserializationException e =
new ODataDeserializationException(
getODataRequest(),
getHttpResponse(),
protocol + " response did not contain any payload.",
null);
oDataRequest.getListeners().forEach(l -> l.listenOnParsingError(e));
throw e;
}
}

Expand Down
Loading