Spring integration, Setting error-handler on http outbound gateway - spring-integration

If I add a custom error-handler to an int-http:outbound-gateway, the response body is not unmarshalled according to the expected-response-type, instead I only get a ResponseEntity returned. My custom error handler is pretty simple:
public class MyResponseErrorHandler extends DefaultResponseErrorHandler {
private static final Logger log = LoggerFactory.getLogger(AlmaGetUserResponseErrorHandler.class);
#Override
public boolean hasError(final ClientHttpResponse response) throws IOException {
// stop http 400 from returning true to error here.
log.debug("Request has returned error code {}", response.getStatusCode());
if (response.getBody() != null) {
String returnBody = IOUtils.toString(response.getBody(), "UTF-8");
log.debug("Checking error from response, code {}, body {}", response.getStatusCode(), returnBody);
}
return false;
}
}
As soon as I remove the error-handler, it unmarshalls the XML response into my POJO correctly.

The issue above was that the MyResponseErrorHandler class was streaming out the body content before it was being passed to the marshaller for expected-response-type. Hence the body was null and a plain ResponseEntity was returned.

Related

spring-integration-kafka: KafkaTemplate#setMessageConverter(RecordMessageConverter) has no effect

I'm trying to set a custom message converter for my Spring Integration Kafka message handler (yes, I know I can supply serializer configsā€”I'm trying to do something a little different).
I have the following:
#Bean
public KafkaTemplate<String, String> kafkaTemplate() {
final KafkaTemplate<String, String> kafkaTemplate = new KafkaTemplate<>(producerFactory());
kafkaTemplate.setMessageConverter(new MessagingMessageConverter() {
#Override
public ProducerRecord<?, ?> fromMessage(final Message<?> message, final String s) {
LOGGER.info("fromMessage({}, {})", message, s);
return super.fromMessage(message, s);
}
});
return kafkaTemplate;
}
#Bean
#ServiceActivator(inputChannel = "kafkaMessageChannel")
public MessageHandler kafkaMessageHandler() {
final KafkaProducerMessageHandler<String, String> handler = new KafkaProducerMessageHandler<>(kafkaTemplate());
handler.setTopicExpression(new LiteralExpression(getTopic()));
handler.setSendSuccessChannel(kafkaSuccessChannel());
return handler;
}
When a message is sent to kafkaMessageChannel, the handler sends it and the result shows up in kafkaSuccessChannel, but the RecordMessageConverter I set in the template was never called
The template message converter is only used when using template.send(Message<?>) which is not used by the outbound channel adapter.
The outbound adapter maps the headers itself using its header mapper; there is no conversion performed on the message payload.
What documentation leads you to believe the converter is used in this context?

Spring Integration Java DSL and Http.outboundGateway: How to get the real error message JSON

How to get the real error message JSON when the Http.outboundGateway call is failed.
For example my program does the HTTP POST. The operation fails with the error code 400 Bad Request and the real error message is (tested with the Postman):
{
"name": [
"This field is needed."
]
}
I have the error channel like this:
#Bean
private IntegrationFlow myErrorChannel() {
return f -> f.handle("myErrorHandler", "handle")
....
;
}
and the Class MyErrorHandler is like this:
#Service
public class MyErrorHandler {
#ServiceActivator
public Message<MessageHandlingException> handle(Message<MessageHandlingException> message) {
...
}
}
Does the MessageHandlingException contain the real error message?
{
"name": [
"This field is needed."
]
}
I debugged the code and checked the MessageHandlingException exception and it seems it doesn't contain the real error message. The detailMessage contains the text 400 Bad Request, but I want to know the real error message.
How to get the real error message?
Edit:
This is working (I'm assigning the real error message to the new payload):
final RestClientResponseException clientException = (RestClientResponseException) messagingHandlingException.getCause();
payload = clientException.getResponseBodyAsString();
The Resttemplate uses a DefaultResponseErrorHandler by default. That one has a logic like:
protected void handleError(ClientHttpResponse response, HttpStatus statusCode) throws IOException {
String statusText = response.getStatusText();
HttpHeaders headers = response.getHeaders();
byte[] body = getResponseBody(response);
Charset charset = getCharset(response);
switch (statusCode.series()) {
case CLIENT_ERROR:
throw HttpClientErrorException.create(statusCode, statusText, headers, body, charset);
case SERVER_ERROR:
throw HttpServerErrorException.create(statusCode, statusText, headers, body, charset);
default:
throw new UnknownHttpStatusCodeException(statusCode.value(), statusText, headers, body, charset);
}
}
An exception from here is thrown to the HttpRequestExecutingMessageHandler which really wraps it into the MessageHandlingException.
Since you say that you can handle the last one via your MyErrorHandler, I would suggest you just take a look into the cause of the MessageHandlingException, and you'll that RestClientResponseException with all the required info from the response.

Spring Integration DSL transformer

Can I have some help here on the below issue:
Calling the transformer to transform the input object to Map Object and calling handler, the handler is missing header values added before.
Why on transforming payload to Map object losing all headers?
//Adding header here setHeader("t", "t");
#ResponseBody
public EmResponse getAuditTrail(#Valid #RequestBody NGAuditTrailEntry auditEntry) {
LOG.info("Audit Service Called, creating new audit " + auditEntry);
AuditCreationFlow.CreateAuditGateway auditGateway = applicationContext.getBean(AuditCreationFlow.CreateAuditGateway.class);
MessageBuilder messageBuilder = MessageBuilder.withPayload(auditEntry).setHeader("t", "t");
Object response = auditGateway.createAudit(messageBuilder.build());
EmResponse res = new EmResponse();
LOG.info("Done with Audit creation. Response " + response);
return res;
}
//Integration flow starts here
public IntegrationFlow createAuditGatewayFlow() {
LOG.debug("Entered to spring integration flow to create the Audit entry");
return IntegrationFlows.from("auditInputChannel")
.handle(auditObjTransformer, "transformToEjbCompatible")
.handle(ejbCaller, "callEjb")
.get();
}
//Transforming payload object to map
#Component
public class AuditObjTransformer {
private final Logger LOG = LoggerFactory.getLogger(this.getClass());
#Transformer
public Object transformToEjbCompatible(NGAuditTrailEntry ngAuditTrailEntry, Map<String, Object> headers) {
LOG.debug("Transforming the NGAuditTrailEntry To AuditEntry object which is EJB compatible");
//#TODO - Tranformation code goes here.
String s = ngAuditTrailEntry.getObjectName();
Map<String, String> m = new HashMap<>();
m.put("x", s);
return m;
}
//Here in this handler, not getting headers what I added in the rest service above.
public class EJBCaller {
private final Logger LOG = LoggerFactory.getLogger(this.getClass());
public Object callEjb(Object payload, Map<String, Object> headers) throws EJBResponseException {
LOG.debug("Calling Audit EJB to crated Audit entry.");
//#TODO EJB calling code goese here.
LOG.debug("Returned from EJB after creating Audit entry. Returned value" + payload);
return payload;
}
If the transform is other than map then no issues in headers.
Thanks,
Siva
callEjb(Object payload, Map<String, Object> headers)
If payload is a Map, you have that payload in the payload and the headers method arguments at the same time.
To make it working and carry exactly headers to that Map argument you should use #Headers annotation on it:
* Annotation which indicates that a method parameter should be bound to the headers of a
* message. The annotated parameter must be assignable to {#link java.util.Map} with
* String keys and Object values.

How to handle multiple possible response types with Retrofit2, Gson and Rx

The API i have to use sucks, and always returns HTTP 200. But sometimes there is proper response:
[{"blah": "blah"}, {"blah": "blah"}]
and sometimes, there is error:
{"error": "Something went wrong", "code": 123}
I'm using Retrofit2 with Gson converter and Rx adapter:
final Api api = new Retrofit.Builder()
.baseUrl(URL)
.client(client)
.addCallAdapterFactory(RxJavaCallAdapterFactory.createWithScheduler(Schedulers.io()))
.addConverterFactory(GsonConverterFactory.create())
.build()
.create(Api.class);
And now, when I receive error response, the onError handler is called with following exception:
java.lang.IllegalStateException: Expected BEGIN_ARRAY but was BEGIN_OBJECT at line 1 column 2 path $
at com.google.gson.stream.JsonReader.beginArray(JsonReader.java:350)
at com.google.gson.internal.bind.CollectionTypeAdapterFactory$Adapter.read(CollectionTypeAdapterFactory.java:80)
at com.google.gson.internal.bind.CollectionTypeAdapterFactory$Adapter.read(CollectionTypeAdapterFactory.java:61)
at retrofit2.converter.gson.GsonResponseBodyConverter.convert(GsonResponseBodyConverter.java:37)
at retrofit2.converter.gson.GsonResponseBodyConverter.convert(GsonResponseBodyConverter.java:25)
at retrofit2.ServiceMethod.toResponse(ServiceMethod.java:117)
at retrofit2.OkHttpCall.parseResponse(OkHttpCall.java:211)
at retrofit2.OkHttpCall.execute(OkHttpCall.java:174)
at retrofit2.adapter.rxjava.RxJavaCallAdapterFactory$RequestArbiter.request(RxJavaCallAdapterFactory.java:171)
at rx.internal.operators.OperatorSubscribeOn$1$1$1.request(OperatorSubscribeOn.java:80)
at rx.Subscriber.setProducer(Subscriber.java:211)
at rx.internal.operators.OperatorSubscribeOn$1$1.setProducer(OperatorSubscribeOn.java:76)
at retrofit2.adapter.rxjava.RxJavaCallAdapterFactory$CallOnSubscribe.call(RxJavaCallAdapterFactory.java:152)
at retrofit2.adapter.rxjava.RxJavaCallAdapterFactory$CallOnSubscribe.call(RxJavaCallAdapterFactory.java:138)
at rx.Observable.unsafeSubscribe(Observable.java:10144)
at rx.internal.operators.OperatorSubscribeOn$1.call(OperatorSubscribeOn.java:94)
at rx.internal.schedulers.CachedThreadScheduler$EventLoopWorker$1.call(CachedThreadScheduler.java:230)
at rx.internal.schedulers.ScheduledAction.run(ScheduledAction.java:55)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:428)
at java.util.concurrent.FutureTask.run(FutureTask.java:237)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:272)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1133)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:607)
at java.lang.Thread.run(Thread.java:761)
How can I solve it? If I could get the response in the onError handler, I could reparse it with proper error model class. But it seems I can't get the raw response.
You can use a custom Gson deserializer to marshal both responses into a single object type. Here is a rough sketch of the idea assuming your current response type is List<Map<String, String>>, you will need to adjust based on your actual return type. I am also making the assumption that the API always returns an array on success --
public class MyResponse {
String error;
Integer code;
List<Map<String, String>> response;
}
interface MyApi {
#GET("/")
Observable<MyResponse> myCall();
}
private class MyResponseDeserializer implements JsonDeserializer<MyResponse> {
public MyResponse deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
throws JsonParseException {
MyResponse response = new MyResponse();
if (json.isJsonArray()) {
// It is an array, parse the data
Type responseType = new TypeToken<List<Map<String, String>>>(){}.getType();
response.response = context.deserialize(json, responseType);
} else {
// Not an array, parse out the error info
JsonObject object = json.getAsJsonObject();
response.code = object.getAsJsonPrimitive("code").getAsInt();
response.error = object.getAsJsonPrimitive("error").getAsString();
}
return response;
}
}
Use the above to create a custom Gson
Gson gson = new GsonBuilder()
.registerTypeAdapter(MyResponse.class, new MyResponseDeserializer())
.create();
use that in your retrofit builder --
.addConverterFactory(GsonConverterFactory.create(gson))
You should also update your interface to return Observable<MyResponse>. You will get both success and error in onNext now. You'll need to inspect the object to determine if it is a successful response (response != null) or not.

GetRawBody() is returning empty for REST requests

I am trying to write the Raw data of my ServiceStack webservice using servicerunner. This is working for SOAP requests but for the REST request GetRawBody() is returning empty.
public override void BeforeEachRequest(IRequest requestContext, T request)
{
Logger.Write(requestContext.GetRawBody());
}
By default web servers only provide a forward-only Request Stream which you can tell ServiceStack to skip deserialization so you can read from the Request Stream by implementing IRequiresRequestStream on your Request DTO:
public class MyRequest : IRequiresRequestStream
{
Stream RequestStream { get; set; }
}
Which will inject the Request Stream instead of deserializing the Request DTO, e.g:
public class object Any(MyRequest request)
{
var requestBody = request.RequestStream.ReadFully().FromUtf8Bytes();
}
Otherwise if you want ServiceStack to deserialize the Request and you want to re-read from the Request Body later yourself you need to tell ServiceStack to buffer the Request using a pre-request filter:
appHost.PreRequestFilters.Add((httpReq, httpRes) => {
httpReq.UseBufferedStream = true;
});

Resources