I have a ws inbound gateway configuration, which accepts soap request. And I have configured a SoapEndpointInterceptor for the same.
<int-ws:inbound-gateway id="inboundWsGateway" request-channel="requestChannel" mapped-request-headers="*" reply-channel="responseChannel" error-channel="errorChannel" />
<bean class="org.springframework.ws.server.endpoint.mapping.UriEndpointMapping">
<property name="defaultEndpoint" ref="inboundWsGateway"/>
<property name="interceptors">
<array>
<ref bean="messageEndpointInterceptor"/>
</array>
</property>
in the MessageEndPointInterceptor.handleRequest() method, I am trying to get the soap header and add new element
public boolean handleRequest(final MessageContext messageContext, final Object endpoint) {
final SoapMessage soapRequestMessage = (SoapMessage) messageContext.getRequest();
final SoapHeaderElement soapHeaderElement = soapRequestMessage.getSoapHeader().addHeaderElement(qname);
}
Since the incoming soap request is not having any soap:header, soapRequestMessage.getSoapHeader() is returning null. Please let me know how to handle this scenario
Your scenario isn't correct.
You receive request, therefore you have to live with it as is. It is immutable, you can't modify it.
Consider some other hooks to provide that additional info, e.g. MessageHeaders after <int-ws:inbound-gateway>.
You can add new SoapHeaderElement only if you are the owner of the message, e.g. if you use SoapMessageFactory directly, or if you send a WebServise message.
Your inbound message modification looks like sacrilege :).
Related
I've used Spring Integration with JMS very successfully before, but we're now using with RabbitMQ / AMQP and having some issues with error handling.
I have an int-amqp:inbound-channel-adapter with with a errorChannel set up to receive any exception, here an ErrorTransformer class inspects the failed Message's cause exception. Then depending on the type of exception either :-
suppresses the exception and transforms into a JSON object that can go to AMQP outbound-channel-adapter as a business reply explaining the failure. Here I want the original message consumed/ACKed.
Or Re-throws the causing exception to let RabbitMQ re-deliver the message, a certain number of times.
I found that re-throwing caused the message to be re-delivered continuously, I then read about StatefulRetryOperationsInterceptorFactoryBean, so added an advice chain to retry 3 times, then I got exception about no message-id, so also added a 'MissingMessageIdAdvice' at the start of the advice chain.
Despite the advice, I still get continuous re-tries for a RuntimeException that is re-thrown from errorChannel's ErrorTransformer. I'm publishing the message via RabbitMQ admin just using the defaults. Not sure if the lack of a message-id is making this not work, if so how do I get a message to have an id ?
I'm confused about the differences between the :-
A) ConditionalRejectingErrorHandler (I've set as the inbound adapter's error-handler) which allows me to provide a customFatalExceptionStrategy to implement isFatal(). Where I believe fatal=true (means DISCARD) and message is consumed and discarded, but how can I still send an outbound failure message ?
B) And the errorChannel I have on the inbound adapter which I'm using to inspect an Exception and transform into a outbound fail response message. Here I guess I could throw AmqpRejectAndDontRequeueException, but then why have the ConditionalRejectingErrorHandler as well ? and will thorwing AmqpRejectAndDontRequeueException work
<int-amqp:inbound-channel-adapter id="amqpInRequestPatternValuation" channel="requestAmqpIn" channel-transacted="true" transaction-manager="transactionManager"
queue-names="requestQueue" error-channel="patternValuationErrorChannel" connection-factory="connectionFactory"
receive-timeout="59000" concurrent-consumers="1"
advice-chain="retryChain" error-handler="customErrorHandler" />
<bean id="customErrorHandler" class="org.springframework.amqp.rabbit.listener.ConditionalRejectingErrorHandler">
<constructor-arg ref="customFatalExceptionStrategy"/>
</bean>
<bean id="customFatalExceptionStrategy" class="abc.common.CustomFatalExceptionStrategy"/>
<!-- ADVICE CHAIN FOR CONTROLLING NUMBER OF RE-TRIES before sending to DLQ (or discarding if no DLQ) without this any re-queued fatal message will retry forever -->
<util:list id="retryChain">
<bean class="org.springframework.amqp.rabbit.retry.MissingMessageIdAdvice">
<constructor-arg>
<bean class="org.springframework.retry.policy.MapRetryContextCache" />
</constructor-arg>
</bean>
<ref bean="retryInterceptor" />
</util:list>
<bean id="retryInterceptor"
class="org.springframework.amqp.rabbit.config.StatefulRetryOperationsInterceptorFactoryBean">
<property name="retryOperations" ref="retryTemplate" />
<property name="messageRecoverer" ref="messageRecoverer"/>
</bean>
<bean id="retryTemplate" class="org.springframework.retry.support.RetryTemplate">
<property name="retryPolicy" ref="simpleRetryPolicy" />
<property name="backOffPolicy">
<bean class="org.springframework.retry.backoff.ExponentialBackOffPolicy">
<property name="initialInterval" value="10000" />
</bean>
</property>
</bean>
<bean id="simpleRetryPolicy" class="org.springframework.retry.policy.SimpleRetryPolicy">
<property name="maxAttempts" value="3" />
</bean>
You have to use RejectAndDontRequeueRecoverer to stop re-delivery in the end of retries:
* MessageRecover that causes the listener container to reject
* the message without requeuing. This enables failed messages
* to be sent to a Dead Letter Exchange/Queue, if the broker is
* so configured.
Yes, the messageId is important for that retry use-case.
You can inject custom MessageKeyGenerator strategy to determine a unique key from message if you can't supply it manually during sending.
I never got around to posting the solution, so here it is.
Once I had configured the retry advice chain to the AMQP inbound channel adapter which must include a messageRecoverer of RejectAndDontRequeueRecoverer (which I believe is also the dafault). The important point I was missing was to ensure that when sender's send a message they include a message_id. So if publishing via the RabbitMQ Admin Console I needed to include the predefined message_id property and supply a value.
Using the 'MissingMessageIdAdvice' doesn't help (so I removed) as it will generate a different message_id on each re-deliver on the message leading to the re-try count not incrementing as each delivery was considered different from the last
I am facing issue with the int-http:outbound-gateway when I am passing a rest-template which holds the basic credentials at the initial time. But, if any one changes credentials in database, rest-template cannot get those updated credential dynamically.
My code,
<int-http:outbound-gateway id="OutboundGateway"
request-channel="sendDataToContentType"
url="http://localhost:8080"
expected-response-type="java.lang.String"
rest-template="restTemplate"/>
<bean id="httpComponentsMessageSender" class="org.springframework.ws.transport.http.HttpComponentsMessageSender">
<property name="credentials">
<bean class="org.apache.http.auth.UsernamePasswordCredentials">
<constructor-arg value="${fromDatabase.userName}"/>
<constructor-arg value="${fromDatabase.password}"/>
</bean>
</property>
<bean id="restTemplate" class="org.springframework.web.client.RestTemplate">
<constructor-arg>
<bean class="org.springframework.http.client.HttpComponentsClientHttpRequestFactory">
<property name="httpClient" value="#{httpComponentsMessageSender.httpClient}"/>
</bean>
</constructor-arg>
Is there any solution for this?
You don't need to do anything with the rest-template. There is need really just update the credentials on the underlying HttpClient.
I'd do that similar to the org.springframework.ws.transport.http.HttpComponentsMessageSender code:
((org.apache.http.impl.client.DefaultHttpClient) getHttpClient())
.getCredentialsProvider().setCredentials(authScope, credentials);
I mean in case of changing credential in the DB you should retrieve that #{httpComponentsMessageSender.httpClient} in your service and call that code.
In fact the credentials are used for each request:
context.setAttribute(
ClientContext.CREDS_PROVIDER,
getCredentialsProvider());
return context;
where it is used from the AbstractHttpClient.doExecute().
So, update the credentials on the HttpClient in one place has effect for the next HTTP Request from the <int-http:outbound-gateway>.
UPDATE
But, in this approach a new httpClient instance is being passed
Right, you can use the HttpClientBuilder but only once, on the initialization phase. I mean you need it for the httpClient injection to the httpComponentsMessageSender.
And the reason for that is because you can pass there (HttpClientBuilder) your own CredentialsProvider. When the same setCredentials(authScope, credentials) can be used from your DB service.
I am using spring integration (4.0.6) to make SOAP calls from rest service using int-http:outbound-gateway and int-ws:outbound-gateway. Is there a way to log SOAP request message only in case of exception.
<int:gateway id="myGateway" service-interface="myservice">
<int:method name="getData" request-channel="reqChannel" reply-channel="repChannel">
</int-gateway>
my outbound-gateway configured as below
<int-http:outbound-gateway id="og1" request-channel="reqChannel" url="xxx" http-method="POST" reply-channel="repChannel" reply-timeout="10000" message-converters="converter" request-factory="reqFactory">
<bean id="reqFactory"
class="org.springframework.http.client.SimpleClientHttpRequestFactory">
<property name="cTimeout" value="20000"/>
<property name="rTimeout" value="20000"/>
</bean>
Ok, Try this method.
Create your own error Handler which implements the spring Error Handler Interface. In your context file
<bean id="soapErrorHandlingSyncTaskExecutor"
class="org.springframework.integration.util.ErrorHandlingTaskExecutor">
<constructor-arg>
<bean class="org.springframework.core.task.SyncTaskExecutor" />
</constructor-arg>
<constructor-arg>
<bean class="ErrorHandle"
p:errorSender-ref="errorSender"/>
</constructor-arg>
</bean>
<bean id="errorSender" class="org.springframework.integration.core.MessagingTemplate"
p:defaultChannel-ref="errors" />
When ever your WS adapter throws any exception then org.springframework.integration.util.ErrorHandlingTaskExecutor catches this exception now its upto you how you handle the exception in your error handler.
The SOAP faultcode and faultstring is found in the MessagingException's cause (if a SoapFaultClientException).
Here i am sending the error messages to a channel for me to monitor.
Hope this helps.
Would be better to share some config and the point where would you like to get a log/exception.
Both, <int-http:outbound-gateway> and <int-ws:outbound-gateway> have <request-handler-advice-chain> ability, where you could use ExpressionEvaluatingRequestHandlerAdvice and send an ErrorMessage with the request (guilty?) message for any diagnostics.
You should configure the readTimeout on the appropriate ClientHttpRequestFactory, e.g. HttpComponentsClientHttpRequestFactory#setReadTimeout(int timeout).
Regarding converters and the wish to log their results in case of error...
How about to implement ClientHttpRequestInterceptor with the desired logic:
try {
return execution.execute(request, body);
catch (Exception e) {
this.errorChannel.send(...);
}
I'm prety sure that the byte[] body is your SOAP request.
See ClientHttpRequestInterceptor JavaDocs for more info.
The ClientHttpRequestInterceptor is part of RestTemplate, which can be injected to the http:outbound-gateway.
For the ws:outbound-gateway you should use ClientInterceptor with the handleFault implementation and extract MessageContext.getRequest() for your purpose.
We are using http outbound adapter to make http get request and we want to read URL from properties file as it changes from envt to envt. We also append some other path to this url using message payload but then it is giving us this error message "Caused by: java.lang.IllegalArgumentException: Map has no value for URL". All we need is read base url from properties file and generate final url with payload.
Here is our sample config looks like
<int-http:outbound-gateway request-channel="requestChannel"
url="${url}/{payload}"
http-method="GET"
expected-response-type="java.lang.String"
>
</int-http:outbound-gateway>
Actually {payload} in your URL is an URI variable and it can't be resolved automatically. See how it works:
UriComponentsBuilder.fromUriString(uri).buildAndExpand(uriVariables)
Where uriVariables is a Map for those URI variables.
So, in your case the expected configuration must be like this:
<int-http:outbound-gateway request-channel="requestChannel"
url="${url}/{payload}"
http-method="GET"
expected-response-type="java.lang.String">
<int-http:uri-variable name="payload" expression="payload"/>
</int-http:outbound-gateway>
More information you can find in the Reference Manual.
I have the following workflow.
inbound-channel
splitter
task executor for split channels - all the threads execute the same workflow.
3.a. construct the request
3.b. service activator wrapper for a gateway message endpoint.
3.c. gateway wrapper over the http-outbound-gateway with the configuration of error-channel (to handle exceptions while invoking http-outbound-gateway)
3.d. http-outbound-gateway
aggregator
response out of spring integration workflow.
If an exception occurs in 3.d, the control goes to the service-activator configured for the gateway error channel.
I copy just the following from the failed message to the new header to the header passed to the error channel.
a. correlationId
b. sequenceNumber
c. sequenceSize
But while aggregating the splitter response, the DefaultAggregatingMessageGroupProcessor.java removes the conflicting headers and by that it removes the error-channel and reply-channel before providing the control to aggregator.
So once the aggregator completes it's operation it is unable to find the reply or error channel and it results in an exception.
I'm using spring-integration-core version 2.2.1 and I'm not sure why the reply-channel and error-channel is being removed during header aggregation.
Any input on resolving this issue will be of great help.
Thank You :)
EDIT 1:
Thank You very much Gary for helping me out with this scenario. I'm sharing my current configuration
<!-- SPLITTER -->
<int:splitter id="dentalSplitter" ref="dentalServiceSplitter"
method="getDentalServiceList" input-channel="dentalServiceSplitterChannel"
output-channel="dentalSplitterTaskChannel" />
<int:channel id="dentalSplitterTaskChannel">
<int:dispatcher task-executor="dentalTaskExecutor" />
</int:channel>
<int:chain input-channel="dentalSplitterTaskChannel" output-channel="dentalGatewayChannel">
<int:header-enricher>
<int:header name="CHAIN_START_TIME" expression="T(System).currentTimeMillis()" overwrite="true" />
<int:object-to-json-transformer content-type="application/json"/>
</int:chain>
<int:service-activator input-channel="dentalGatewayChannel" ref="dentalGatewayWrapper" output-channel="dentalReplyChannel" />
<int:gateway id="dentalGatewayWrapper" default-request-channel="dentalCostEstimateRequestChannel" error-channel="dentalErrorChannel"/>
<int-http:outbound-gateway id="dentalGateway"
url-expression="#urlBuilder.build('${service.endpoint}')"
http-method="POST" request-factory="clientHttpRequestFactory"
request-channel="dentalCostEstimateRequestChannel" extract-request-payload="true"
expected-response-type="com.dental.test.DentalResponse">
<int-http:request-handler-advice-chain>
<ref bean="logChainTimeInterceptor" />
</int-http:request-handler-advice-chain>
</int-http:outbound-gateway>
<!-- EXCEPTION -->
<int:chain input-channel="dentalErrorChannel" output-channel="dentalAggregatorChannel">
<int:transformer ref="commonErrorTransformer" method="dentalGracefulReturn"/>
</int:chain>
<!-- SUCCESS -->
<int:chain input-channel="dentalReplyChannel" output-channel="dentalAggregatorChannel">
<int:filter discard-channel="dentalErrorChannel"
expression="T(com.dental.util.InvocationOutcomeHelper).isOutcomeSuccess(payload?.metadata?.outcome?.code,payload?.metadata?.outcome?.message)" />
</int:chain>
<!-- AGGREGATION -->
<int:chain input-channel="dentalAggregatorChannel" output-channel="wsDentalServiceOutputChannel" >
<int:aggregator ref="dentalServiceAggregator" />
<int:service-activator ref="dentalResponseServiceActivator" />
</int:chain>
What I noticed was this, every split channel when passing through the gateway creates a new temporary channel for error and reply and after getting the response back from the gateway, it retains the preserved (original inbound) error and reply channel header. And as you had mentioned, after the control gets to the error transformer that flow of retaining the preserved headers gets broken and the aggregating message group processor receives three different instances of temporary channel and hence removes them.
I was planning to have a custom message group processor and modify the conflict resolution strategy for aggregating the header and came up with this config.
<bean id="channelPreservingAggregatingMessageHandler" class="org.springframework.integration.aggregator.AggregatingMessageHandler">
<constructor-arg name="processor" ref="channelPreservingMessageGroupProcessor"/>
</bean>
I'm yet to test this out though. But based on this discussion, this does not look like a viable solution.
And looks like my configuration for error handling in gateway is incorrect.
However, I'm confused on this statement of yours "Instead of forwarding the message directly, simply handle the error on your error flow and return the result normally to the gateway "wrapper"". If I remove the error channel how will I get the control back when an exception occurs? May be I'm missing to understand something here. Can you elaborate more on this please?
When asking questions about scenarios such as this, you generally need to show your configuration. However, I suspect you are forwarding the message from the error flow directly to the aggregator.
This is like doing a GOTO in code and breaks the scoping.
It won't work because the replyChannel header in the error message is for the gateway "wrapper", not the original upstream inbound gateway. When the aggregator gets conflicting headers, it has no choice but to drop the headers (you will see a DEBUG log message to that effect).
Instead of forwarding the message directly, simply handle the error on your error flow and return the result normally to the gateway "wrapper" (simply omit the error channel on the last element on the error flow).
The gateway will then fix up the reply so it is consistent with other messages (good and bad) and forward it to the aggregator.
You don't need to mess with headers in your error flow, just return the value you want to be aggregated along with the good results.
You should really update to a current release, or at least the latest in the 2.2.x line (2.2.6).