Spring-WS Wss4jSecurityInterceptor doesn't decrypt received message - spring

My case is as follows:
I wrote SOAP service using CXF
I wrote two SOAP clients - one using CXF and another using Spring-WS
I set up WSSecurity : "Timestamp Signature Encrypt" action on both sides (client / server)
CXF client works like charm, but Spring-WS cant decrypt response.
Server side is OK while interacting with CXF-client and Spring-client (it is properely decrypting, verifing signature, processing requests, sign, encrypt and finnaly send response).
Code:
CXF client:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:jaxws="http://cxf.apache.org/jaxws" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://cxf.apache.org/jaxws http://cxf.apache.org/schemas/jaxws.xsd">
<bean id="inbound-logging" class="org.apache.cxf.interceptor.LoggingInInterceptor" />
<bean id="outbound-logging" class="org.apache.cxf.interceptor.LoggingOutInterceptor" />
<jaxws:client id="helloClient" serviceClass="com.example.HelloWorld"
address="http://localhost:8282/HelloWorld">
<jaxws:inInterceptors>
<ref bean="inbound-logging" />
<ref bean="inbound-security" />
</jaxws:inInterceptors>
<jaxws:outInterceptors>
<ref bean="outbound-logging" />
<ref bean="outbound-security" />
</jaxws:outInterceptors>
</jaxws:client>
<bean class="org.apache.cxf.ws.security.wss4j.WSS4JOutInterceptor" id="outbound-security">
<constructor-arg>
<map>
<entry key="action" value="Timestamp Signature Encrypt"/>
<entry key="user" value="client"/>
<entry key="signaturePropFile" value="config/client-crypto.properties"/>
<entry key="encryptionPropFile" value="config/client-crypto.properties"/>
<entry key="signatureKeyIdentifier" value="DirectReference"/>
<entry key="encryptionUser" value="server"/>
<entry key="passwordCallbackClass" value="org.mydomain.ClientPasswordCallback"/>
<entry key="signatureParts" value="{Element}{http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd}Timestamp;{Element}{http://schemas.xmlsoap.org/soap/envelope/}Body"/>
<entry key="encryptionParts" value="{Element}{http://www.w3.org/2000/09/xmldsig#}Signature;{Content}{http://schemas.xmlsoap.org/soap/envelope/}Body"/>
<entry key="encryptionSymAlgorithm" value="http://www.w3.org/2001/04/xmlenc#tripledes-cbc"/>
</map>
</constructor-arg>
</bean>
<bean class="org.apache.cxf.ws.security.wss4j.WSS4JInInterceptor" id="inbound-security">
<constructor-arg>
<map>
<entry key="action" value="Timestamp Signature Encrypt"/>
<entry key="signaturePropFile" value="config/client-crypto.properties"/>
<entry key="decryptionPropFile" value="config/client-crypto.properties"/>
<entry key="passwordCallbackClass" value="org.mydomain.CustomPasswordCallback"/>
</map>
</constructor-arg>
</bean>
Spring-WS client - wss-interceptor :
#Bean
public Wss4jSecurityInterceptor wssInterceptor(
#Qualifier("cryptoFactoryBean") CryptoFactoryBean cryptoFactoryBean,
#Qualifier("signValidCryptoFactoryBean") CryptoFactoryBean signValidCryptoFactoryBean) throws Exception {
Wss4jSecurityInterceptor interceptor = new Wss4jSecurityInterceptor();
// outgoing securement
interceptor.setSecurementUsername("client");
interceptor.setSecurementPassword("123456");
interceptor.setSecurementSignatureKeyIdentifier("DirectReference");
interceptor.setSecurementActions("Timestamp Signature Encrypt");
interceptor.setSecurementSignatureCrypto(cryptoFactoryBean.getObject());
interceptor.setSecurementSignatureParts(
"{Element}{http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd}Timestamp;" +
"{Element}{http://schemas.xmlsoap.org/soap/envelope/}Body"
);
interceptor.setSecurementEncryptionCrypto(cryptoFactoryBean.getObject());
interceptor.setSecurementEncryptionUser("server");
interceptor.setSecurementEncryptionSymAlgorithm("http://www.w3.org/2001/04/xmlenc#tripledes-cbc");
interceptor.setSecurementEncryptionParts(
"{Element}{http://www.w3.org/2000/09/xmldsig#}Signature;" +
"{Content}{http://schemas.xmlsoap.org/soap/envelope/}Body"
);
// incoming validation:
interceptor.setValidationActions("Timestamp Signature Encrypt");
interceptor.setValidationDecryptionCrypto(cryptoFactoryBean.getObject());
interceptor.setValidationSignatureCrypto(signValidCryptoFactoryBean.getObject());
return interceptor;
}
Logging level is set to debug. Here is the result:
(... - logs related with encrypting data befrore send )
2015-08-09 19:09:45.181 DEBUG WSSecEncrypt:258 - Encryption complete.
2015-08-09 19:09:45.182 DEBUG sent:620 - Sent request [SaajSoapMessage {http://www.w3.org/2001/04/xmlenc#}EncryptedData]
2015-08-09 19:09:45.223 DEBUG received:678 - Received response [SaajSoapMessage {http://www.w3.org/2001/04/xmlenc#}EncryptedData] for request [SaajSoapMessage {http://www.w3.org/2001/04/xmlenc#}EncryptedData]
2015-08-09 19:09:45.224 DEBUG Wss4jSecurityInterceptor:562 - Validating message [SaajSoapMessage {http://www.w3.org/2001/04/xmlenc#}EncryptedData] with actions [Timestamp Signature Encrypt]
2015-08-09 19:09:45.225 DEBUG WSSecurityEngine:236 - enter processSecurityHeader()
2015-08-09 19:09:45.229 WARN Wss4jSecurityInterceptor:281 - Could not validate request: No WS-Security header found
2015-08-09 19:09:45.229 DEBUG Wss4jSecurityInterceptor:288 - No exception resolver present, creating basic soap fault
Exception in thread "main" org.springframework.oxm.UnmarshallingFailureException: JAXB unmarshalling exception; nested exception is javax.xml.bind.UnmarshalException: unexpected element (uri:"http://schemas.xmlsoap.org/soap/envelope/", local:"Fault"). Expected elements are <{}helloRequest>,<{http://example.com}helloResponse>
at org.springframework.oxm.jaxb.Jaxb2Marshaller.convertJaxbException(Jaxb2Marshaller.java:809)
at org.springframework.oxm.jaxb.Jaxb2Marshaller.unmarshal(Jaxb2Marshaller.java:730)
at org.springframework.ws.support.MarshallingUtils.unmarshal(MarshallingUtils.java:62)
at org.springframework.ws.client.core.WebServiceTemplate$3.extractData(WebServiceTemplate.java:407)
at org.springframework.ws.client.core.WebServiceTemplate.doSendAndReceive(WebServiceTemplate.java:596)
at org.springframework.ws.client.core.WebServiceTemplate.sendAndReceive(WebServiceTemplate.java:537)
at org.springframework.ws.client.core.WebServiceTemplate.marshalSendAndReceive(WebServiceTemplate.java:384)
at org.springframework.ws.client.core.WebServiceTemplate.marshalSendAndReceive(WebServiceTemplate.java:374)
at client.Main.main(Main.java:39)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:497)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:134)
Caused by: javax.xml.bind.UnmarshalException: unexpected element (uri:"http://schemas.xmlsoap.org/soap/envelope/", local:"Fault"). Expected elements are <{}helloRequest>,<{http://example.com}helloResponse>
at com.sun.xml.internal.bind.v2.runtime.unmarshaller.UnmarshallingContext.handleEvent(UnmarshallingContext.java:726)
at com.sun.xml.internal.bind.v2.runtime.unmarshaller.Loader.reportError(Loader.java:247)
at com.sun.xml.internal.bind.v2.runtime.unmarshaller.Loader.reportError(Loader.java:242)
at com.sun.xml.internal.bind.v2.runtime.unmarshaller.Loader.reportUnexpectedChildElement(Loader.java:109)
at com.sun.xml.internal.bind.v2.runtime.unmarshaller.UnmarshallingContext$DefaultRootLoader.childElement(UnmarshallingContext.java:1131)
at com.sun.xml.internal.bind.v2.runtime.unmarshaller.UnmarshallingContext._startElement(UnmarshallingContext.java:556)
at com.sun.xml.internal.bind.v2.runtime.unmarshaller.UnmarshallingContext.startElement(UnmarshallingContext.java:538)
at com.sun.xml.internal.bind.v2.runtime.unmarshaller.InterningXmlVisitor.startElement(InterningXmlVisitor.java:60)
at com.sun.xml.internal.bind.v2.runtime.unmarshaller.SAXConnector.startElement(SAXConnector.java:153)
at com.sun.xml.internal.bind.unmarshaller.DOMScanner.visit(DOMScanner.java:229)
at com.sun.xml.internal.bind.unmarshaller.DOMScanner.scan(DOMScanner.java:112)
at com.sun.xml.internal.bind.v2.runtime.unmarshaller.UnmarshallerImpl.unmarshal0(UnmarshallerImpl.java:354)
at com.sun.xml.internal.bind.v2.runtime.unmarshaller.UnmarshallerImpl.unmarshal(UnmarshallerImpl.java:337)
at javax.xml.bind.helpers.AbstractUnmarshallerImpl.unmarshal(AbstractUnmarshallerImpl.java:127)
at org.springframework.oxm.jaxb.Jaxb2Marshaller.unmarshal(Jaxb2Marshaller.java:726)
... 12 more
It looks like Spring is trying to process response without decrypting it earlier.
I've found solution to the problem. I didn't set password callback, so wss4j-interceptor couldn't decrypt response. Here is code that fix the issue:
KeyStoreCallbackHandler keyStoreCallbackHandler = new KeyStoreCallbackHandler();
keyStoreCallbackHandler.setPrivateKeyPassword("123456");
interceptor.setValidationCallbackHandler(keyStoreCallbackHandler);

You are receiving a soap fault from the other party. Soap fault messages are not encrypted this is why Wss4jSecurityInterceptor is complaining about the ws-security header.
2015-08-09 19:09:45.229 WARN Wss4jSecurityInterceptor:281 -
**Could not validate request: No WS-Security header found**
It seems also that the processing of the message continue while it should normaly stops. The JAXB marshaller complains that (uri:"http://schemas.xmlsoap.org/soap/envelope/", local:"Fault") is not from the context.
2015-08-09 19:09:45.229 DEBUG Wss4jSecurityInterceptor:288 -
No exception resolver present, creating basic soap fault
Exception in thread "main"
org.springframework.oxm.UnmarshallingFailureException:
JAXB unmarshalling exception; nested exception is
javax.xml.bind.UnmarshalException: unexpected element
(uri:"http://schemas.xmlsoap.org/soap/envelope/", local:"Fault").
Expected elements are <{}helloRequest>,<{http://example.com}helloResponse>
I would recommend the following:
Log the soap fault to determine what the other party needs
Whenever you receive a soap fault, you should stop processing the request

KeyStoreCallbackHandler wasn't defined for the interceptor - that was the case. Appropriate code in the initial post.

Related

Spring SAML: SAML message intended destination endpoint did not match recipient endpoint

I am getting 'Caused by: org.opensaml.xml.security.SecurityException: SAML message intended destination endpoint did not match recipient endpoint' exception while SSO between my app SP and client IdP.
Server log show the difference in schemas, see below:
Checking SAML message intended destination endpoint against receiver endpoint
2019-03-05 15:02:44.599 DEBUG [204 default task-41][BaseSAMLMessageDecoder] Intended message destination endpoint: https://my.app.com/app-gateway/saml/SSO
2019-03-05 15:02:44.599 DEBUG [204 default task-41][BaseSAMLMessageDecoder] Actual message receiver endpoint: http://my.app.com/app-gateway/saml/SSO
2019-03-05 15:02:44.600 ERROR [204 default task-41][BaseSAMLMessageDecoder] SAML message intended destination endpoint 'https://my.app.com/app-gateway/saml/SSO' did not match the recipient endpoint 'http://my.app.com/app-gateway/saml/SSO'
My application is running on STG on 2 instances with the LB in front, therefore I use SAMLContextProviderLB context provider instead of SAMLContextProviderImpl:
<bean id="contextProvider" class="org.springframework.security.saml.context.SAMLContextProviderLB">
<property name="scheme" value="https"/>
<property name="serverName" value="my.app.com"/>
<property name="serverPort" value="443"/>
<property name="includeServerPortInRequestURL" value="false"/>
<property name="contextPath" value="/app-gateway"/>
</bean>
<bean id="metadataGeneratorFilter" class="org.springframework.security.saml.metadata.MetadataGeneratorFilter">
<constructor-arg>
<bean class="org.springframework.security.saml.metadata.MetadataGenerator">
<property name="entityBaseURL" value="https://my.app.com/app-gateway1"/>
<property name="entityId" value="${cas.sso.entityId}"/>
<property name="includeDiscoveryExtension" value="false"/>
<property name="extendedMetadata" ref="extendedMetadata"/>
<property name="keyManager" ref="keyManager"/>
</bean>
</constructor-arg>
</bean>
In the source code of getActualReceiverEndpointURI the receiver endpoint URL is being taken from request httpRequest obj. Thus, I am trying to understand at which step that wrong URL http://my.app.com/app-gateway/saml/SSO was set to it. Can anyone explain me it?
protected String getActualReceiverEndpointURI(SAMLMessageContext messageContext) throws MessageDecodingException {
InTransport inTransport = messageContext.getInboundMessageTransport();
if (! (inTransport instanceof HttpServletRequestAdapter)) {
log.error("Message context InTransport instance was an unsupported type: {}",
inTransport.getClass().getName());
throw new MessageDecodingException("Message context InTransport instance was an unsupported type");
}
HttpServletRequest httpRequest = ((HttpServletRequestAdapter)inTransport).getWrappedRequest();
StringBuffer urlBuilder = httpRequest.getRequestURL();
return urlBuilder.toString();
}
You might want to check the following page :
https://developer.jboss.org/thread/240113
I had a similar issue, even with X-Forwarded-Proto properly set on the LB, the request was still interpreted in http only.
The backend must be aware of the header.
add proxy-address-forwarding="true" on the http listener and two filter-ref
<http-listener name="default" socket-binding="http" proxy-address-forwarding="true"/>
<filter-ref name="server-header"/>
<filter-ref name="x-powered-by-header"/>
Hope this help,
For Apache Tomcat server, which is running behind AWS Application load balancer, need to enable the RemoteIPValue so that based on the x-forwarded-proto header, Tomcat will overwrite scheme(https) & port(443) accordingly.
In server.xml
<Valve className="org.apache.catalina.valves.RemoteIpValve" protocolHeader="X-Forwarded-Proto" internalProxies="10\.\d+\.\d+\.\d+|192\.168\.\d+\.\d+|169\.254\.\d+\.\d+|127\.\d+\.\d+\.\d+|172\.(1[6-9]|2[0-9]|3[0-1])\.\d+\.\d+" />

Spring WS SOAP fault response code 500 instead of 400

Currently I am using Spring WS (spring-ws-core:2.1.0.RELEASE)
I have setup global method security:
<security:global-method-security pre-post-annotations="enabled" secured-annotations="enabled"/>
I have an endpoint that throws an AccessDeniedException which I handle with SoapFaultMappingExceptionResolver like this:
<bean id="exceptionResolver"
class="org.springframework.ws.soap.server.endpoint.SoapFaultMappingExceptionResolver">
<property name="defaultFault" value="SERVER"/>
<property name="exceptionMappings">
<value>
org.springframework.security.access.AccessDeniedException=SENDER, 403 Forbidden
</value>
</property>
</bean>
I get the following response
<env:Envelope xmlns:env="http://www.w3.org/2003/05/soap-envelope">
<env:Header/>
<env:Body>
<env:Fault>
<env:Code>
<env:Value>env:Sender</env:Value>
</env:Code>
<env:Reason>
<env:Text xml:lang="en">403 Forbidden</env:Text>
</env:Reason>
</env:Fault>
</env:Body>
</env:Envelope>
with an HTTP response code 500.
According to the spec soap 1.2 spec
it should return 400.
Is there a mistake on my part or ist just the way Spring WS handles it?

Spring integration errorChannel, error handle

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.

How to get header value in bean method

I have custom bean which i use it in spring integration flow. It has method which takes hashmap as argument and its values are set dynamically. I am trying to set the value from the payload header field(iploc) but i am not able to achieve so. I tried some combination of spel but it not work. Any pointers?
<int:transformer id="ws.transformer"
input-channel="ws.transformer.in" output-channel="ws.transformer.out">
<bean class="com.my.Mybean">
<property name="map">
<map>
<entry key="user">
<value>"admin"</value>
</entry>
<entry key="location">
<value>"headers['iploc']"</value>
</entry>
</map>
</property>
</bean>
</int:transformer>
I can alternatively set the value in Service activator, but i am trying if i achieve this in the SI config itself.
Spring Integration Transformer can consume entire Message (payload and headers) so there is no point in passing header value by property since transformer already has access to all message headers.
Your transformer bean definition should contain only these properties that do not come from the Message being transformed:
<int:transformer id="ws.transformer" input-channel="ws.transformer.in" output-channel="ws.transformer.out">
<bean class="com.my.MyTransformer">
<property name="user" value="admin"/>
</bean>
</int:transformer>
And your transformer method:
#Transformer
OutgoingPayload transform(IncomingPayload payload, #Header("iploc") String iplocHeader) {
return doTransform(...);
}
or just consume entire message:
Message<OutgoingPayload> transform(Message<IncomingPayload> message) {
final String ipLocHeaderValue = message.getHeaders.get("iploc", String.class);
return doTransform(...);
}
The bean defined within a transformer like that will be instantiated at startup time, not each time a message is received. The normal way to handle a requirement like this is to have a stateless bean with a method that accepts the header value upon each invocation.

How can I use a list as reference in Spring configuration for JAX-WS handlers?

I am using the Spring extension for JAX-WS and have a problem to use a list in my Spring configuration:
This is the working configuration:
<wss:binding url="/services/demo">
<wss:service>
<ws:service bean="#demoEndpoint">
<ws:handlers>
<ref bean="handler1"/>
<ref bean="handler2"/>
</ws:handlers>
</ws:service>
</wss:service>
</wss:binding>
As I have more endpoint, which should use the same set of handlers, I want to define the list of handlers in a single configuration.
But this config doesn't work:
<util:list id="myHandlers">
<ref bean="handler1"/>
<ref bean="handler2"/>
</util:list>
<wss:binding url="/services/demo">
<wss:service>
<ws:service bean="#demoEndpoint">
<ws:handlers>
<ref bean="myHandlers"/>
</ws:handlers>
</ws:service>
</wss:service>
</wss:binding>
I get the following exception:
Caused by: java.lang.IllegalStateException: Cannot convert value of type [java.util.ArrayList] to required type [javax.xml.ws.handler.Handler] for property 'handlers[0]': no matching editors or conversion strategy found
at org.springframework.beans.TypeConverterDelegate.convertIfNecessary(TypeConverterDelegate.java:264)
at org.springframework.beans.TypeConverterDelegate.convertToTypedCollection(TypeConverterDelegate.java:559)
at org.springframework.beans.TypeConverterDelegate.convertIfNecessary(TypeConverterDelegate.java:199)
at org.springframework.beans.BeanWrapperImpl.convertIfNecessary(BeanWrapperImpl.java:448)
... 66 more
This is the corresponding setter in org.jvnet.jax_ws_commons.spring.SpringService:
public void setHandlers(List<Handler> handlers) {
this.handlers = handlers;
}

Resources