I have the next configuration:
Inbound
<int:gateway id="inGateway" service-interface="XXX"
error-channel="errorChannel"
default-request-channel="requestChannel"
default-reply-channel="replyChannel" />
Outbound:
<ws:outbound-gateway id="ws-outbound-gateway"
request-channel="inbound" reply-channel="outbound" uri="XXX" />
Chain 1:
<int:chain input-channel="requestChannel" output-channel="inbound">
XXX </int:chain>
Chain 2:
<int:chain input-channel="outbound" output-channel="replyChannel"> XXX
</int:chain>
Error:
<int:chain input-channel="errorChannel" output-channel="replyChannel">
<int:transformer ref="logicTransformers" method="errorTransformerMethod"></int:transformer>
</int:chain>
</beans>
Java Transformer:
final GenericError errorCatalog = errorCatalog(errorMessage);
LOGGER.warn("Transformed error from catalog: {}", errorCatalog);
final MessageBuilder<Document> builder =
MessageBuilder.withPayload(XmlUtil.parseToDocument(errorCatalog)).copyHeaders(errorMessage.getHeaders()).copyHeadersIfAbsent(errorMessage.getHeaders());
When the webservice of outbound is down the error go into errorChannel transformer but to reponse we have the next error:
o.s.m.c.GenericMessagingTemplate$TemporaryReplyChannel#242 Reply
message received but the receiving thread has exited due to an
exception while sending the request message:GenericMessage
[payload=[#document: null], headers={spanTraceId=8bf90ea9ff4266c8,
spanId=598680bae5f913d5, spanParentSpanId=8bf90ea9ff4266c8,
replyChannel=org.springframework.messaging.core.GenericMessagingTemplate$TemporaryReplyChannel#76c751de,
functionalId=PRUEBASOA12,
errorChannel=org.springframework.messaging.core.GenericMessagingTemplate$TemporaryReplyChannel#76c751de,
messageSent=true, id=2108386f-99db-98b9-3d30-3bcf45335424,
spanSampled=1, spanName=message:requestChannel}]
We don't understand... because we have the same flow with
In case of error on the service level the original message is wrapped to the MessagingException and the ErrorMessage with that payload is sent to the errorChannel. Even if you use replyChannel explicitly, you still have to ensure the replyChannel header, which is populated by the inbound gateway. And exactly this header plays the main role in the request-reply behavior in that gateway.
That replyHeader is still present in your error flow, but it is already a part of the failedMessage in the MessagingException payload.
So, your logicTransformers.errorTransformerMethod should extract that replyChannel header for the real reply from this method to be sent to the explicit replyChannel. Or you can omit this replyChannel at all because any way the real sendAndReceive is based on the replyChannel header.
Please, read more info in the Reference Manual:
https://docs.spring.io/spring-integration/docs/4.3.11.RELEASE/reference/html/messaging-endpoints-chapter.html#gateway
https://docs.spring.io/spring-integration/docs/4.3.11.RELEASE/reference/html/configuration.html#namespace-errorhandler
Related
I want to invoke an external REST endpoint POST request with a JSON payload which would be getting called from another service through the http inbound gateway.
I am using the below configuration for my application :
<int:channel id="xappSearchRequest" />
<int:channel id="xappSearchResponse" />
<int:channel id="xappFilterChannelOutput"/>
<int:channel id="discardFilterChannel"/>
<int:channel id="mutableMessageChannel"/>
<int:filter input-channel="mutableMessageChannel" output-channel="xappFilterChannelOutput" discard-channel="discardFilterChannel" ref="structureValidationFilter"/>
<int:transformer input-channel="xappSearchRequest" output-channel="mutableMessageChannel"
ref="mutableMessageTransformer" />
<int-http:inbound-gateway id="inboundxappSearchRequestGateway"
supported-methods="POST"
request-channel="xappSearchRequest"
reply-channel="xappSearchResponse"
mapped-response-headers="Return-Status, Return-Status-Msg, HTTP_RESPONSE_HEADERS"
path="${xapp.request.path}"
reply-timeout="50000"
request-payload-type="standalone.CFIRequestBody">
</int-http:inbound-gateway>
<int:service-activator id="xappServiceActivator"
input-channel="xappFilterChannelOutput"
output-channel="xappSearchResponse"
ref="xappSearchService"
method="handlexappRequest"
requires-reply="true"
send-timeout="60000"/>
<int:service-activator id="dicardPayloadServiceActivator"
input-channel="discardFilterChannel"
output-channel="xappSearchResponse"
ref="invalidPayloadService"
method="getInvalidMessage"
requires-reply="true"
send-timeout="60000"/>
<int-http:outbound-gateway id="get.outbound.gateway"
request-channel="get_send_channel" url="${cms.stub.request.url}"
http-method="POST" reply-channel="get_receive_channel"
expected-response-type="standalone.StubResponseBody">
</int-http:outbound-gateway>
Not able to figure out how to send a JSON payload and custom headers to call the POST endpoint.
Custom headers can be mapped to HTTP headers using an appropriate property - mapped-request-headers.
There is a full documentation on the matter: https://docs.spring.io/spring-integration/docs/5.2.3.RELEASE/reference/html/http.html#http-header-mapping
For JSON request the <int-http:outbound-gateway> is supplied with the RestTemplate which has a MappingJackson2HttpMessageConverter configured if you have a jackson-databind on classpath. Only what you need from your application is to send a POJO which can be serialized into a JSON and what is important - a MessageHeaders.CONTENT_TYPE header with an application/json value.
Figured that out .
I am using a outbound gateway like below :
<int-http:outbound-gateway id="get.outbound.gateway"
request-channel="postCmsChannel" url="${cms.stub.request.url}"
http-method="POST" reply-channel="zappSearchResponse"
expected-response-type="standalone.StubResponseBody">
</int-http:outbound-gateway>
The 'postCmsChannel' is the output-channel of service activator :
<int:service-activator id="zappServiceActivator"
input-channel="xappFilterChannelOutput"
output-channel="postCmsChannel"
ref="xappSearchService"
method="handleXappRequest"
send-timeout="60000"/>
The 'xappSearchService' is like below :
#Autowired
#Qualifier("postCmsChannel")
MessageChannel postCmsChannel;
public void handleXappRequest(Message<CFIRequestBody> inMessage){
/*
* Map<String, Object> responseHeaderMap = new HashMap<String, Object>();
*
* //MessageHeaders headers = inMessage.getHeaders(); StubResponseBody
* info=inMessage.getPayload();
*
* setReturnStatusAndMessage(responseHeaderMap); Message<StubResponseBody>
* message = new GenericMessage<StubResponseBody>(info, responseHeaderMap);
* return message;
*/
inMessage.getHeaders();
Map<String,String> headerMap=new HashMap<String,String>();
headerMap.put("X-JWS-SIGNATURE", "dshdgshdgasshgdywtwtqsabh232wgd7wdyt");
headerMap.put("X-PARTICIPANT-ID", "CMDRV2112BB");
postCmsChannel.send(MessageBuilder.withPayload(inMessage.getPayload())
.copyHeadersIfAbsent(inMessage.getHeaders()).copyHeadersIfAbsent(headerMap).build());
}
}
I would like to get the message from JMS and send it as HTTP request and in case of failure, enqueue it again to JMS.
I've tried using inbound-message-adapter and message-driven-channel-adapter, but it fails as I get "ChannelResolutionException: no output-channel or replyChannel header available" exception but since I do not want to reply to inbound-message-adapter, not sure why would I include a replyChannel header
<jms:outbound-channel-adapter id="outboundJMSAdaptor" jms-template="jmsTemplate"
channel="jmsOutChannel"
destination="requestQueue"/>
<int:channel id="jmsInChannel" />
<jms:message-driven-channel-adapter
channel="jmsInChannel" destination="requestQueue"
connection-factory="jmsConnectionFactory" message-converter="jmsMessageConverter"/>
<int:header-enricher input-channel="jmsInChannel" output-channel="header_enriched_request">
<int:header name="addressId" expression="payload.getId()"/>
<int:header name="Accept-Language" value="en_GB"/>
<int:header name="X-Source-CountryCode" value="GB"/>
<int:header name="X-Source-Operator" value="Enterprise"/>
<int:header name="X-Source-Division" value="CustomerManagement"/>
<int:header name="X-Source-System" value="${sapwebservices.http.header.source.system}"/>
<int:header name="X-Source-Timestamp" expression="new java.text.SimpleDateFormat('yyyy-MM-dd HH:mm:ss').format(new java.util.Date())"/>
<int:header name="Accept" value="application/json"/>
<int:header name="Content-Type" value="application/json;charset=UTF-8"/>
</int:header-enricher>
<int:object-to-json-transformer input-channel="header_enriched_request"
output-channel="update_customer_shipping_address_outbound_gateway"
object-mapper="nonNullObjectMapper"/>
<http:outbound-gateway mapped-request-headers="Accept*, Content-Type, X-*, HTTP_REQUEST_HEADERS"
request-channel="update_customer_shipping_address_outbound_gateway"
reply-channel="print_payload_update_shipping"
url="${sapwebservices.ws.uri.updatecustomershippingaddress}"
http-method="PUT"
expected-response-type="java.lang.String"
charset="UTF-8"
request-factory="updateCustomerAccountRequestFactory">
<http:uri-variable name="id" expression="headers['addressId']"/>
</http:outbound-gateway>
<int:service-activator input-channel="print_payload_update_shipping" output-channel="clean_no_print_char_update_customershippingaddress" ref="sapPrintPayload"/>
<int:transformer input-channel="clean_no_print_char_update_customershippingaddress" output-channel="resp_mapping_json_to_jsonobj_updatecustomershippingaddress">
<bean class="util.CleanNoPrintCharTransformer"/>
</int:transformer>
<int:json-to-object-transformer input-channel="resp_mapping_json_to_jsonobj_updatecustomershippingaddress"
output-channel="clean_no_print_char_update_customershippingaddress"
type="customer_shipping_address_response.json.CustomerShippingAddressResponse"/>
<int:transformer input-channel="clean_no_print_char_update_customershippingaddress" output-channel="">
<bean class="transformer.CreateCustomerShippingAddressPostTransformer"/>
</int:transformer>
I expect a success in a normal run, getting error
org.springframework.integration.dispatcher.AggregateMessageDeliveryException: All attempts to deliver Message to MessageHandlers failed. Multiple causes:
All attempts to deliver Message to MessageHandlers failed. Multiple causes:
no output-channel or replyChannel header available
org.springframework.integration.MessageHandlingException: org.springframework.expression.spel.SpelEvaluationException: EL1004E:(pos 8): Method call: Method transform(rest.bbr.customer_shipping_address_response.json.CustomerShippingAddressResponse) cannot be found on bbr.sap.util.CleanNoPrintCharTransformer type
See below for the stacktrace of the first cause.
org.springframework.integration.MessageHandlingException: org.springframework.expression.spel.SpelEvaluationException: EL1004E:(pos 8): Method call: Method transform(java.lang.String) cannot be found on bbr.sap.transformer.CreateCustomerShippingAddressPostTransformer type
See below for the stacktrace of the first cause.
Also, would like to enqueue the message again to the jms queue if the http:outbound-gateway respond as null or if the 3rd party services are down.
<int:transformer input-channel="clean_no_print_char_update_customershippingaddress"
output-channel="">
You can't have an empty output channel on a transformer.
It doesn't really make sense to transform something and then just discard the transformed result but if that's what you really want to do, send it to the nullChannel
<int:transformer input-channel="clean_no_print_char_update_customershippingaddress"
output-channel="nullChannel">
Scenario could be: my expectation could be 10 datapoint in batch, and I want to give response for {failed 5, pass 5} or sth.
my logic is to split the batch into data element and do validation.
successful validation will send to aggreagtor,
failed validation will throw error and pick up by error channel.
recipient-list-router take the errorChannel as inputChannel and 2 filter connect to it, the purpose is to filter some type of error to send response directly(eception unrelated to user input - server error or etc) and some type of client side error will go to aggregator to build response.
Is any problem with the logic?
My problem is I keep getting "Reply message received but the receiving thread has already received a reply" when build up the Result using service-activator after aggregator. this service-activator connect to replyChannel and it seems like there are some message already sent to this channel?
I checked my integration work flow only this service-activator and server error branch after "error filter" connect to replyChannel(but the handle is never called.)
Something wrong? BTW, can recipient-list-router or other type of endpoint connect to errorChannel? or it has to be service-activator as what I saw in all the example online?(but they are really simple example..)
Sample XML
<int:gateway id="myGateway" service-interface="someGateway" default-request-channel="splitChannel" error-channel="errorChannel" default-reply-channel="replyChannel" async-executor="MyThreadPoolTaskExecutor"/>
<int:splitter input-channel="splitChannel" output-channel="transformChannel" method="split">
<bean class="Splitter" />
</int:splitter>
<int:transformer id="transformer" input-channel="transformChannel" method="transform" output-channel="aggregateChannel">
<bean class="Transformer"/> // this may throw the validation error (filter_ErrorType_1), if it cannot transform
</int:transformer>
<int:aggregator id="aggregator"
input-channel="aggregateChannel"
output-channel="createAnswerChannel"
method="aggregate">
<bean class="MyAggregator" />
</int:aggregator>
<int:recipient-list-router id="myErrorRouter" input-channel="errorChannel">
<int:recipient channel="filter_ErrorType_1"/>
<int:recipient channel="filter_ErrorType_2"/>
<int:recipient channel="filter_ErrorType_3"/>
</int:recipient-list-router>
<int:filter input-channel="filter_ErrorType_1" output-channel="aggregateChannel" method="accept"></int:filter>
<int:filter input-channel="filter_ErrorType_2" output-channel="createErrorAnswerChannel" method="accept"></int:filter>
<int:filter input-channel="filter_ErrorType_3" output-channel="createErrorAnswerChannel" method="accept"></int:filter>
<int:service-activator input-channel='createErrorAnswerChannel' output-channel="replyChannel" method='buildError'>
<bean class="AnswerBuilder"/>
</int:service-activator>
<int:service-activator input-channel='createAnswerChannel' output-channel="replyChannel" method='build'>
<bean class="AnswerBuilder"/>
</int:service-activator>
Follow up:
<int:gateway id="myGateway" service-interface="someGateway" default-request-channel="splitChannel" error-channel="errorChannel" default-reply-channel="replyChannel" async-executor="MyThreadPoolTaskExecutor"/>
<int:splitter input-channel="splitChannel" output-channel="transformChannel" method="split">
<bean class="Splitter" />
</int:splitter>
<int:transformer id="transformer1" input-channel="toTransformer1" method="transform" output-channel="toTransformer2">
<bean class="Transformer1"/> // this may throw the validation error (filter_ErrorType_1), if it cannot transform
</int:transformer>
<int:transformer id="transformer2" input-channel="toTransformer2" method="transform" output-channel="toTransformer3">
<bean class="Transformer2"/> // this may throw the validation error (filter_ErrorType_2), if it cannot transform
</int:transformer>
<int:transformer id="transformer3" input-channel="toTransformer3" method="transform" output-channel="aggregateChannel">
<bean class="Transformer3"/> // this may throw the validation error (filter_ErrorType_3), if it cannot transform
</int:transformer>
???
// seems like you are proposing to have one gateway for each endpoint that may throw error.
// but in this case, take transfomer 1 for example, I cannot output the gateway directly to aggregate channel since for valid data it has to go to transformer 2
// but the failed message throwed by the error handler cannot pass transformer 2 because of afterall this is a error message not a valid data for transformer 2
// <int:service-activator input-channel="toTransformer1" output-channel="toTransformer2" ref="gateway1"/>
// <int:gateway id="gateway1" default-request-channel="toTransformer1" error-channel="errorChannel1"/>
// <int:transformer id="transformer" input-channel="toTransformer1" method="transform">
// <bean class="Transformer"/> // this may throw the validation error (filter_ErrorType_1), if it cannot transform
// </int:transformer>
// how to deal with this problem?
<int:service-activator input-channel='createErrorAnswerChannel' output-channel="replyChannel" method='buildError'>
<bean class="AnswerBuilder"/>
</int:service-activator>
<int:service-activator input-channel='createAnswerChannel' output-channel="replyChannel" method='build'>
<bean class="AnswerBuilder"/>
</int:service-activator>
I actually have complicated logic inside but I use transformer 123 to represent here.
Your question isn't clear. Please,be sure in the future provide more concrete info. Some config and StackTrace or logs are useful, too.
I guess that you have in the beginning of your flow <gateway> with configured error-channel. That's why you are receiving Reply message received but the receiving thread has already received a reply.
But I can't be sure, because there is no those words in your question. Right?
You can't rely on the errorChannel header there and come back for the reply eventually because the errorChannel header in case of Gateway is the same as replyChannel, and it is TemporaryReplyChannel - one-shot-usage channel and only for receive() operation.
Since we don't have any configuration or code from you we can't help you properly.
I suppose you need a middle-flow gateway via service-activator:
<service-activator id="gatewayTestService" input-channel="inputChannel"
output-channel="outputChannel" ref="gateway"/>
<gateway id="gateway" default-request-channel="requestChannel" error-channel="myErrorChannel"/>
or <chain> with <gateway> to perform validation and catch errors only there and return a desired reply for failures. That service-activator will be able to send them all to the <aggregator> afterward. In this case the <aggregator> can reply back to the gateway properly.
EDIT
errorChannel is a name for default global bean to catch any error messages from any Integration place. See more info in the http://docs.spring.io/spring-integration/reference/html/configuration.html#namespace-errorhandler.
So, that name is fully bad for your use case , because that myErrorRouter is going to handle ALL the errors!
The <int:recipient-list-router> sends message to all its recipients if it passes a selector or if there is no selector.
You don't need default-reply-channel on the <gateway> if it isn't publish-subscribe. You can just rely on the replyChannel header, which works if there is no output-channel defined.
What I'm talking about <service-activator> and <gateway> is pretty straight forward in front of your <transformer> after <splitter>:
<int:service-activator input-channel="transformChannel"
output-channel="aggregateChannel" ref="gateway"/>
<int:gateway id="gateway" default-request-channel="transformChannel" error-channel="validationErrorChannel"/>
<int:transformer id="transformer" input-channel="transformChannel" method="transform">
<bean class="Transformer"/> // this may throw the validation error (filter_ErrorType_1), if it cannot transform
</int:transformer>
So, splitter sends items to the service-activator. That one proceeds with the message to the gateway around transformer with custom error-channel exactly for one item. The transformer answers to the replyChannel exactly to that previous gateway. If it throws some Exception, it is handled by the validationErrorChannel process. Which should reply with some compensation message. That message is bubbled to the service-activator. Finally service-activator sends a result to the aggregateChannel. And it is black box for the service-activator if validation was good or not.
Hope that helps a bit.
EDIT2
I'm disappointed that you haven't accepted my advises in your code, but nevertheless that is how I see it:
<int:gateway id="myGateway" service-interface="someGateway" default-request-channel="splitChannel"
async-executor="MyThreadPoolTaskExecutor" />
<int:splitter input-channel="splitChannel" output-channel="transformChannel" method="split">
<bean class="Splitter" />
</int:splitter>
<int:service-activator input-channel="validateChannel" output-channel="aggregateChannel"
ref="validateGateway"/>
<gateway id="validateGateway" default-request-channel="toTransformer1" error-channel="myErrorChannel"/>
<chain input-channel="toTransformer1">
<int:transformer method="transform">
<bean class="Transformer1" />
</int:transformer>
<int:transformer method="transform">
<bean class="Transformer2" />
</int:transformer>
<int:transformer method="transform">
<bean class="Transformer3" />
</int:transformer>
</chain>
<int:service-activator input-channel="myErrorChannel" method="buildError">
<bean class="AnswerBuilder" />
</int:service-activator>
<int:aggregator id="aggregator"
input-channel="aggregateChannel"
output-channel="createAnswerChannel"
method="aggregate">
<bean class="MyAggregator" />
</int:aggregator>
<int:service-activator input-channel='createAnswerChannel' method='build'>
<bean class="AnswerBuilder" />
</int:service-activator>
Pay attention, how I chain transformers. So, you have one gateway for all your transformers and any error on any of them will be thrown to the gateway for error handling on the myErrorChannel.
I am trying to unit test an xpath router, but having problems with it. here is my context file:
<int:channel id="toTransactionTypeRouterChannel" />
<int-xml:xpath-router id="transactionsTypeRouter"
input-channel="toTransactionTypeRouterChannel" resolution-required="false"
evaluate-as-string="true" default-output-channel="errorChannel">
<!-- Select node name of the first child -->
<int-xml:xpath-expression
expression="name(/soapNs:Envelope/soapNs:Body/schNs:processArchiveRequest/schNs:fulfillmentRequest/schNs:requestDetail/*[1])"
namespace-map="archiveNamespaceMap" />
<int-xml:mapping value="sch:bulkRequestDetail"
channel="bulkChannel" />
<int-xml:mapping value="sch:transactionalRequestDetail"
channel="transactionChannel" />
</int-xml:xpath-router>
<int:channel id="bulkChannel" />
<int:channel id="transactionChannel" />
<int:transformer input-channel="bulkChannel"
output-channel="consoleOut" expression="'Bulk channel has received the payload' " />
<int:transformer input-channel="transactionChannel"
output-channel="consoleOut" expression="'Transaction channel has received payload' " />
<int:transformer input-channel="errorChannel"
output-channel="consoleOut" expression="'Error channel has received payload' " />
As you can see here, there are 2 different routes(bulk,trans.) + error channel.Here is my unit test case for trans channel route:
#Test
public void testTransactionFlow() throws Exception {
try {
Resource bulkRequest = new FileSystemResource("src/main/resources/mock-message-examples/SampleProcessArchiveTransRequest.xml");
String transRequestStr= extractResouceAsString(bulkRequest);
toTransactionTypeRouterChannel.send(MessageBuilder.withPayload(transRequestStr).build());
Message<?> outMessage = testChannel.receive(0);
assertNotNull(outMessage);
context file for junit
<int:bridge input-channel="transactionChannel"
output-channel="testChannel"/>
<int:channel id="testChannel">
<int:queue/>
</int:channel>
As you can see, in the junit context file, I am connecting transactional channel to the test channel.in the junit test case, I am sending a payload to the router in the junit method, and trying to receive it from the input channel and use it for assertion. however the assertion fails, as the message from transaction channel directly goes to consoleOut before getting routed to inputChannel as given in the junit coontext file. How do I intercept the message before it goes to consoleOut? I also tried adding wireTap interceptors but they didnt work:
WireTap wireTap = new WireTap(someChannel);
boolean w = wireTap.isRunning();
transactionChannel.addInterceptor(wireTap);
Basically, I need a separate flow for unit testing.
With that configuration, you are just adding a second consumer to transactionChannel - messages will be round-robin distributed to the transformer and bridge.
You can unsubscribe the transformer for your test case by autowiring it by id as an EventDrivenConsumer and stop() it before sending your message.
/*Below Spring-integration configuration*/
/*Listener*/
<!--Reading the Message from Queue as a Listener -->
<int-jms:inbound-gateway connection-factory="MQConnectionFactory"
request-destination="ReadWsRequestQueue"
request-channel="ReadWsInputChannel"
transaction-manager="hibernateTransactionManager"
error-channel="errorReadChannel"/>
/*Processing Message*/
<!--Processing the message-->
<int:chain input-channel="ReadWsInputChannel">
<int:transformer ref="ReadUnmarshaller"/>
<int:transformer ref="RequestBuilder" method="build"/>
<int:service-activator ref="ReadService" method="registerRead" />
<int:transformer ref="ResponseBuilder" method="buildResponse"/>
<int:transformer ref="ReadMarshaller"/>
<int:transformer ref="toStringTransformer"/>
/*Error Channel config*/
<!--Error channel configuration -->
<int:channel id="errorReadChannel"/>
<int:chain input-channel="errorReadChannel">
<int-jms:outbound-gateway id="jmsOutboundGateway"
connection-factory="MQConnectionFactory"
request-destination="DLQErrorQueue" />
</int:chain>
Question:
In service Activator method we are throwing a RuntimeException, which should get redirected to the error channel??
All the exceptions should go to the error-channel,
Question 2.)
Also is there any way in Spring-integration by which we can forward the exception causing actual MQ message to a separate channel?