Skip to content

Commit 8cba9e7

Browse files
committed
Handle errors when parsing the http response in a configurable way.
Allows to run a cf java client against a cf instance of a later version.
1 parent a8f7c09 commit 8cba9e7

13 files changed

Lines changed: 733 additions & 36 deletions

cloudfoundry-client-reactor/src/main/java/org/cloudfoundry/reactor/util/JsonCodec.java

Lines changed: 54 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@
1616

1717
package org.cloudfoundry.reactor.util;
1818

19+
import com.fasterxml.jackson.core.JsonPointer;
1920
import com.fasterxml.jackson.core.JsonProcessingException;
21+
import com.fasterxml.jackson.databind.JsonMappingException;
2022
import com.fasterxml.jackson.databind.ObjectMapper;
2123
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
2224
import io.netty.handler.codec.http.HttpHeaderNames;
@@ -25,7 +27,10 @@
2527
import io.netty.handler.codec.json.JsonObjectDecoder;
2628
import java.nio.charset.Charset;
2729
import java.util.function.BiFunction;
30+
import org.cloudfoundry.reactor.util.JsonDeserializationProblemHandler.RetryException;
2831
import org.reactivestreams.Publisher;
32+
import org.slf4j.Logger;
33+
import org.slf4j.LoggerFactory;
2934
import reactor.core.Exceptions;
3035
import reactor.core.publisher.Mono;
3136
import reactor.netty.ByteBufFlux;
@@ -35,6 +40,7 @@
3540
public final class JsonCodec {
3641

3742
private static final int MAX_PAYLOAD_SIZE = 100 * 1024 * 1024;
43+
private static final Logger LOGGER = LoggerFactory.getLogger("cloudfoundry-client");
3844

3945
public static <T> Mono<T> decode(
4046
ObjectMapper objectMapper, ByteBufFlux responseBody, Class<T> responseType) {
@@ -43,17 +49,57 @@ public static <T> Mono<T> decode(
4349
.asByteArray()
4450
.map(
4551
payload -> {
46-
try {
47-
return objectMapper.readValue(payload, responseType);
48-
} catch (Throwable t) {
49-
throw new JsonParsingException(
50-
t.getMessage(),
51-
t,
52-
new String(payload, Charset.defaultCharset()));
53-
}
52+
return decodeInternal(objectMapper, payload, responseType);
5453
});
5554
}
5655

56+
// decode the payload into an object.
57+
// If that fails, check the exception and retry.
58+
private static <T> T decodeInternal(
59+
ObjectMapper objectMapper, byte[] payload, Class<T> responseType) {
60+
try {
61+
return objectMapper.readValue(payload, responseType);
62+
} catch (Throwable t) {
63+
T result = null;
64+
if (t instanceof JsonMappingException) {
65+
Throwable cause = t.getCause();
66+
if (cause != null) {
67+
if (cause instanceof RetryException) {
68+
result = retry(objectMapper, responseType, (RetryException) cause, payload);
69+
if (result != null) {
70+
return result;
71+
}
72+
}
73+
}
74+
}
75+
if (t instanceof RetryException) {
76+
result = retry(objectMapper, responseType, (RetryException) t, payload);
77+
if (result != null) {
78+
return result;
79+
}
80+
}
81+
String payloadStr = new String(payload, Charset.defaultCharset());
82+
LOGGER.warn("unable to parse the following json message:");
83+
LOGGER.warn(payloadStr);
84+
throw new JsonParsingException(t.getMessage(), t, payloadStr);
85+
}
86+
}
87+
88+
// drop the problematic element from the payload and try again.
89+
private static <T> T retry(
90+
ObjectMapper objectMapper,
91+
Class<T> responseType,
92+
RetryException cause,
93+
byte[] payload) {
94+
String property = cause.getProperty();
95+
JsonPointer pointer = cause.getJsonPointer(); // JsonPointer.compile(pointerStr);
96+
payload = JsonDeserializationProblemHandler.dropProperty(payload, property, pointer);
97+
if (payload != null) {
98+
return decodeInternal(objectMapper, payload, responseType);
99+
}
100+
return null;
101+
}
102+
57103
public static void setDecodeHeaders(HttpHeaders httpHeaders) {
58104
httpHeaders.set(HttpHeaderNames.ACCEPT, HttpHeaderValues.APPLICATION_JSON);
59105
}

0 commit comments

Comments
 (0)