Even when using a Marshaller , you may also provide a reference for the request-callback on the outbound gateways.
|
For either outbound gateway type, you can specify a destination-provider
attribute instead of the uri
(exactly one of them is required).
You can then reference any Spring Web Services DestinationProvider
implementation (for example, to lookup the URI from a registry at runtime).
For either outbound gateway type, the message-factory
attribute can also be configured with a reference to any Spring Web Services WebServiceMessageFactory
implementation.
For the simple inbound gateway type, you can set the extract-payload
attribute to false
to forward the entire WebServiceMessage
instead of just its payload as a Message
to the request channel.
Doing so might be useful, for example, when a custom transformer works against the WebServiceMessage
directly.
Starting with version 5.0, the web-service-template
reference attribute lets you inject a WebServiceTemplate
with any possible custom properties.
37.4. Outbound URI Configuration
For all URI schemes supported by Spring Web Services (see URIs and Transports) <uri-variable/>
substitution is provided.
The following example shows how to define it:
<ws:outbound-gateway id="gateway" request-channel="input"
uri="https://springsource.org/{thing1}-{thing2}">
<ws:uri-variable name="thing1" expression="payload.substring(1,7)"/>
<ws:uri-variable name="thing2" expression="headers.x"/>
</ws:outbound-gateway>
<ws:outbound-gateway request-channel="inputJms"
uri="jms:{destination}?deliveryMode={deliveryMode}&priority={priority}"
message-sender="jmsMessageSender">
<ws:uri-variable name="destination" expression="headers.jmsQueue"/>
<ws:uri-variable name="deliveryMode" expression="headers.deliveryMode"/>
<ws:uri-variable name="priority" expression="headers.jms_priority"/>
</ws:outbound-gateway>
If you supply a DestinationProvider
, variable substitution is not supported and a configuration error occurs if you provide variables.
37.4.1. Controlling URI Encoding
By default, the URL string is encoded (see UriComponentsBuilder
) to the URI object before sending the request.
In some scenarios with a non-standard URI, it is undesirable to perform the encoding.
Since version 4.1, the <ws:outbound-gateway/>
element provides an encode-uri
attribute.
To disable encoding the URL, set this attribute false
(it defaults to true
).
If you wish to partially encode some of the URL, you can do so by using an expression
within a <uri-variable/>
, as the following example shows:
<ws:outbound-gateway url="http://somehost/%2f/fooApps?bar={param}" encode-uri="false">
<http:uri-variable name="param"
expression="T(org.apache.commons.httpclient.util.URIUtil)
.encodeWithinQuery('Hello World!')"/>
</ws:outbound-gateway>
If you set DestinationProvider , encode-uri is ignored.
|
37.5. WS Message Headers
The Spring Integration web service gateways automatically map the SOAP action header.
By default, it is copied to and from Spring Integration MessageHeaders
by using the DefaultSoapHeaderMapper
.
You can pass in your own implementation of SOAP-specific header mappers, as the gateways have properties to support doing so.
Unless explicitly specified by the requestHeaderNames
or replyHeaderNames
properties of the DefaultSoapHeaderMapper
, any user-defined SOAP headers are not copied to or from a SOAP Message.
When you use the XML namespace for configuration, you can set these properties by using the mapped-request-headers
and mapped-reply-headers
attributes, you can provide a custom mapper by setting the header-mapper
attribute.
When mapping user-defined headers, the values can also contain simple wildcard patterns (such myheader* or myheader ).
For example, if you need to copy all user-defined headers, you can use the wildcard character: .
|
Starting with version 4.1, the AbstractHeaderMapper
(a DefaultSoapHeaderMapper
superclass) lets the NON_STANDARD_HEADERS
token be configured for the requestHeaderNames
and replyHeaderNames
properties (in addition to existing STANDARD_REQUEST_HEADERS
and STANDARD_REPLY_HEADERS
) to map all user-defined headers.
Rather than using the wildcard (* ), we recommend using the following combination : STANDARD_REPLY_HEADERS, NON_STANDARD_HEADERS .
Doing so avoids mapping request headers to the reply.
|
Starting with version 4.3, you can negate patterns in the header mappings by preceding the pattern with !
.
Negated patterns get priority, so a list such as STANDARD_REQUEST_HEADERS,thing1,thing*,!thing2,!thing3,qux,!thing1
does not map thing1
, thing2
, or thing3
.
It does map the standard headers, thing4
, and qux
.
(Note that thing1
is included in both non-negated and negated forms.
Because negated values take precedence, thing1
is not mapped.)
If you have a user-defined header that begins with ! that you do wish to map, you can escape it with \ , as follows: STANDARD_REQUEST_HEADERS,\!myBangHeader .
A !myBangHeader is then mapped.
|
Inbound SOAP headers (request headers for the inbound gateway and reply headers for the outbound gateway) are mapped as SoapHeaderElement
objects.
You can explore the contents by accessing the Source
:
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
<soapenv:Header>
<auth>
<username>user</username>
<password>pass</password>
</auth>
<bar>BAR</bar>
<baz>BAZ</baz>
<qux>qux</qux>
</soapenv:Header>
<soapenv:Body>
...
</soapenv:Body>
</soapenv:Envelope>
If mapped-request-headers
is auth, ca*
, the auth
, cat
, and can
headers are mapped, but qux
is not mapped.
The following example shows how to get a value named user
from a header named auth
:
...
SoapHeaderElement header = (SoapHeaderElement) headers.get("auth");
DOMSource source = (DOMSource) header.getSource();
NodeList nodeList = source.getNode().getChildNodes();
assertEquals("username", nodeList.item(0).getNodeName());
assertEquals("user", nodeList.item(0).getFirstChild().getNodeValue());
...
Starting with version 5.0, the DefaultSoapHeaderMapper
supports user-defined headers of type javax.xml.transform.Source
and populates them as child nodes of the <soapenv:Header>
.
The following example shows how to do so:
Map<String, Object> headers = new HashMap<>();
String authXml =
"<auth xmlns='http://test.auth.org'>"
+ "<username>user</username>"
+ "<password>pass</password>"
+ "</auth>";
headers.put("auth", new StringSource(authXml));
...
DefaultSoapHeaderMapper mapper = new DefaultSoapHeaderMapper();
mapper.setRequestHeaderNames("auth");
The result of the preceding examples is the following SOAP envelope:
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
<soapenv:Header>
<auth xmlns="http://test.auth.org">
<username>user</username>
<password>pass</password>
</auth>
</soapenv:Header>
<soapenv:Body>
...
</soapenv:Body>
</soapenv:Envelope>
37.6. MTOM Support
The marshalling inbound and outbound web service gateways support attachments directly through built-in functionality of the marshaller (for example, Jaxb2Marshaller
provides the mtomEnabled
option).
Starting with version 5.0, the simple web service gateways can directly operate with inbound and outbound MimeMessage
instances, which have an API to manipulate attachments.
When you need to send web service message with attachments (either a reply from a server or a client request) you should use the WebServiceMessageFactory
directly and send a WebServiceMessage
with attachments as a payload
to the request or reply channel of the gateway.
The following example shows how to do so:
WebServiceMessageFactory messageFactory = new SaajSoapMessageFactory(MessageFactory.newInstance());
MimeMessage webServiceMessage = (MimeMessage) messageFactory.createWebServiceMessage();
String request = "<test>foo</test>";
TransformerFactory transformerFactory = TransformerFactory.newInstance();
Transformer transformer = transformerFactory.newTransformer();
transformer.transform(new StringSource(request), webServiceMessage.getPayloadResult());
webServiceMessage.addAttachment("myAttachment", new ByteArrayResource("my_data".getBytes()), "plain/text");
this.webServiceChannel.send(new GenericMessage<>(webServiceMessage));
38. XML Support - Dealing with XML Payloads
Spring Integration’s XML support extends the core of Spring Integration with the following components:
You need to include this dependency into your project:
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-xml</artifactId>
<version>5.1.5.RELEASE</version>
</dependency>
compile "org.springframework.integration:spring-integration-xml:5.1.5.RELEASE"
These components make working with XML messages in Spring Integration simpler.
The messaging components work with XML that is represented in a range of formats, including instances of java.lang.String
, org.w3c.dom.Document
, and javax.xml.transform.Source
.
However, where a DOM representation is required (for example, in order to evaluate an XPath expression), the String
payload is converted into the required type and then converted back to String
.
Components that require an instance of DocumentBuilder
create a namespace-aware instance if you do not provide one.
When you require greater control over document creation, you can provide an appropriately configured instance of DocumentBuilder
.
38.1. Namespace Support
All components within the Spring Integration XML module provide namespace support. In order to enable namespace support, you need to import the schema for the Spring Integration XML Module. The following example shows a typical setup:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:int="http://www.springframework.org/schema/integration"
xmlns:int-xml="http://www.springframework.org/schema/integration/xml"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/integration
https://www.springframework.org/schema/integration/spring-integration.xsd
http://www.springframework.org/schema/integration/xml
https://www.springframework.org/schema/integration/xml/spring-integration-xml.xsd">
</beans>
38.1.1. XPath Expressions
Many of the components within the Spring Integration XML module work with XPath Expressions.
Each of those components either references an XPath Expression that has been defined as a top-level element or uses a nested <xpath-expression/>
element.
All forms of XPath expressions result in the creation of an XPathExpression
that uses the Spring org.springframework.xml.xpath.XPathExpressionFactory
.
When XPath expressions are created, the best XPath implementation that is available on the classpath is used (either JAXP 1.3+ or Jaxen, with JAXP being preferred).
| Internally, Spring Integration uses the XPath functionality provided by the Spring Web Services project (www.spring.io/spring-ws). Specifically, we use the Spring Web Services XML module (spring-xml-x.x.x.jar). For a deeper understanding, see the respective documentation at docs.spring.io/spring-ws/docs/current/reference/#xpath. |
Here is an overview of all available configuration parameters of the xpath-expression
element:
The following listing shows the available attributes for the xpath-expression
element:
<int-xml:xpath-expression expression="" (1)
id="" (2)
namespace-map="" (3)
ns-prefix="" (4)
ns-uri=""> (5)
<map></map> (6)
</int-xml:xpath-expression>
| 1 | Defines an XPath expression. Required. |
| 2 | The identifier of the underlying bean definition.
It is an instance of org.springframework.xml.xpath.XPathExpression .
Optional. |
| 3 | Reference to a map that contains namespaces.
The key of the map defines the namespace prefix, and the value of the map sets the namespace URI.
It is not valid to specify both this attribute and the map element or the ns-prefix and ns-uri attributes.
Optional. |
| 4 | Lets you set the namespace prefix directly as an attribute on the XPath expression element.
If you set ns-prefix , you must also set the ns-uri attribute.
Optional. |
| 5 | Lets you directly set the namespace URI as an attribute on the XPath expression element.
If you set ns-uri , you must also set the ns-prefix attribute.
Optional. |
| 6 | Defines a map that contains namespaces.
Only one map child element is allowed.
The key of the map defines the namespace prefix, and the value of the map sets the namespace URI.
It is not valid to specify both this element and the map attribute or set the ns-prefix and ns-uri attributes.
Optional. |
Providing Namespaces (Optional) to XPath Expressions
For the XPath Expression Element, you can provide namespace information as configuration parameters. You can define namespaces by using one of the following choices:
-
Reference a map by using the
namespace-map
attribute -
Provide a map of namespaces by using the
map
sub-element -
Specify the
ns-prefix
andns-uri
attributes
All three options are mutually exclusive. Only one option can be set.
The following example shows several different ways to use XPath expressions, including the options for setting the XML namespaces mentioned earlier:
<int-xml:xpath-filter id="filterReferencingXPathExpression"
xpath-expression-ref="refToXpathExpression"/>
<int-xml:xpath-expression id="refToXpathExpression" expression="/name"/>
<int-xml:xpath-filter id="filterWithoutNamespace">
<int-xml:xpath-expression expression="/name"/>
</int-xml:xpath-filter>
<int-xml:xpath-filter id="filterWithOneNamespace">
<int-xml:xpath-expression expression="/ns1:name"
ns-prefix="ns1" ns-uri="www.example.org"/>
</int-xml:xpath-filter>
<int-xml:xpath-filter id="filterWithTwoNamespaces">
<int-xml:xpath-expression expression="/ns1:name/ns2:type">
<map>
<entry key="ns1" value="www.example.org/one"/>
<entry key="ns2" value="www.example.org/two"/>
</map>
</int-xml:xpath-expression>
</int-xml:xpath-filter>
<int-xml:xpath-filter id="filterWithNamespaceMapReference">
<int-xml:xpath-expression expression="/ns1:name/ns2:type"
namespace-map="defaultNamespaces"/>
</int-xml:xpath-filter>
<util:map id="defaultNamespaces">
<util:entry key="ns1" value="www.example.org/one"/>
<util:entry key="ns2" value="www.example.org/two"/>
</util:map>
Using XPath Expressions with Default Namespaces
When working with default namespaces, you may run into situations that behave differently than you might expect. Assume we have the following XML document (which represents an order of two books):
<?xml version="1.0" encoding="UTF-8"?>
<order>
<orderItem>
<isbn>0321200683</isbn>
<quantity>2</quantity>
</orderItem>
<orderItem>
<isbn>1590596439</isbn>
<quantity>1</quantity>
</orderItem>
</order>
This document does not declare a namespace. Therefore, applying the following XPath Expression works as expected:
<int-xml:xpath-expression expression="/order/orderItem" />
You might expect that the same expression also works for the following XML file:
<?xml version="1.0" encoding="UTF-8"?>
<order xmlns="http://www.example.org/orders">
<orderItem>
<isbn>0321200683</isbn>
<quantity>2</quantity>
</orderItem>
<orderItem>
<isbn>1590596439</isbn>
<quantity>1</quantity>
</orderItem>
</order>
The preceding example looks exactly the same as the previous example but declares a default namespace.
However, the previous XPath expression (/order/orderItem
) fails in this case.
In order to solve this issue, you must provide a namespace prefix and a namespace URI either by setting the ns-prefix
and ns-uri
attributes or by setting the namespace-map
attribute.
The namespace URI must match the namespace declared in your XML document.
In the preceding example, that is www.example.org/orders
.
You can, however, arbitrarily choose the namespace prefix. In fact, providing an empty string actually works. (However, null is not allowed.) In the case of a namespace prefix consisting of an empty string, your Xpath expression must use a colon (":") to indicate the default namespace. If you leave off the colon, the XPath expression does not match. The following XPath Expression matches against the XML document in the preceding example:
<int-xml:xpath-expression expression="/:order/:orderItem"
ns-prefix="" ns-uri="https://www.example.org/prodcuts"/>
You can also provide any other arbitrarily chosen namespace prefix.
The following XPath expression (which use the myorder
namespace prefix) also matches:
<int-xml:xpath-expression expression="/myorder:order/myorder:orderItem"
ns-prefix="myorder" ns-uri="https://www.example.org/prodcuts"/>
The namespace URI is the really important piece of information, not the prefix. The Jaxen summarizes the point very well:
In XPath 1.0, all unprefixed names are unqualified. There is no requirement that the prefixes used in the XPath expression are the same as the prefixes used in the document being queried. Only the namespace URIs need to match, not the prefixes.
38.2. Transforming XML Payloads
This section covers how to transform XML payloads
38.2.1. Configuring Transformers as Beans
This section will explain the workings of the following transformers and how to configure them as beans:
All of the XML transformers extend either AbstractTransformer
or AbstractPayloadTransformer
and therefore implement Transformer
.
When configuring XML transformers as beans in Spring Integration, you would normally configure the Transformer
in conjunction with a MessageTransformingHandler
.
This lets the transformer be used as an endpoint.
Finally, we discuss the namespace support , which allows for configuring the transformers as elements in XML.
UnmarshallingTransformer
An UnmarshallingTransformer
lets an XML Source
be unmarshalled by using implementations of the Spring OXM Unmarshaller
.
Spring’s Object/XML Mapping support provides several implementations that support marshalling and unmarshalling by using JAXB, Castor, JiBX, and others.
The unmarshaller requires an instance of Source
.
If the message payload is not an instance of Source
, conversion is still attempted.
Currently, String
, File
, byte[]
and org.w3c.dom.Document
payloads are supported.
To create a custom conversion to a Source
, you can inject an implementation of a SourceFactory
.
If you do not explicitly set a SourceFactory , the property on the UnmarshallingTransformer is, by default, set to a DomSourceFactory .
|
Starting with version 5.0, the UnmarshallingTransformer
also supports an org.springframework.ws.mime.MimeMessage
as the incoming payload.
This can be useful when we receive a raw WebServiceMessage
with MTOM attachments over SOAP .
See MTOM Support for more information.
The following example shows how to define an unmarshalling transformer:
<bean id="unmarshallingTransformer" class="o.s.i.xml.transformer.UnmarshallingTransformer">
<constructor-arg>
<bean class="org.springframework.oxm.jaxb.Jaxb2Marshaller">
<property name="contextPath" value="org.example" />
</bean>
</constructor-arg>
</bean>
Using MarshallingTransformer
The MarshallingTransformer
lets an object graph be converted into XML by using a Spring OXM Marshaller
.
By default, the MarshallingTransformer
returns a DomResult
.
However, you can control the type of result by configuring an alternative ResultFactory
, such as StringResultFactory
.
In many cases, it is more convenient to transform the payload into an alternative XML format.
To do so, configure a ResultTransformer
.
Spring integration provides two implementations, one that converts to String
and another that converts to Document
.
The following example configures a marshalling transformer that transforms to a document:
<bean id="marshallingTransformer" class="o.s.i.xml.transformer.MarshallingTransformer">
<constructor-arg>
<bean class="org.springframework.oxm.jaxb.Jaxb2Marshaller">
<property name="contextPath" value="org.example"/>
</bean>
</constructor-arg>
<constructor-arg>
<bean class="o.s.i.xml.transformer.ResultToDocumentTransformer"/>
</constructor-arg>
</bean>
By default, the MarshallingTransformer
passes the payload object to the Marshaller
.
However, if its boolean extractPayload
property is set to false
, the entire Message
instance is passed to the Marshaller
instead.
That may be useful for certain custom implementations of the Marshaller
interface, but, typically, the payload is the appropriate source object for marshalling when you delegate to any of the various Marshaller
implementations.
XsltPayloadTransformer
The XsltPayloadTransformer
transforms XML payloads by using Extensible Stylesheet Language Transformations (XSLT).
The transformer’s constructor requires an instance of either Resource or Templates to be passed in.
Passing in a Templates
instance allows for greater configuration of the TransformerFactory
used to create the template instance.
As with the UnmarshallingTransformer
, the XsltPayloadTransformer
does the actual XSLT transformation against instances of Source
.
Therefore, if the message payload is not an instance of Source
, conversion is still attempted.
String
and Document
payloads are supported directly.
To create a custom conversion to a Source
, you can inject an implementation of a SourceFactory
.
If a SourceFactory is not set explicitly, the property on the XsltPayloadTransformer is, by default, set to a DomSourceFactory .
|
By default, the XsltPayloadTransformer
creates a message with a Result
payload, similar to the XmlPayloadMarshallingTransformer
.
You can customize this by providing a ResultFactory
or a ResultTransformer
.
The following example configures a bean that works as an XSLT payload transformer:
<bean id="xsltPayloadTransformer" class="o.s.i.xml.transformer.XsltPayloadTransformer">
<constructor-arg value="classpath:org/example/xsl/transform.xsl"/>
<constructor-arg>
<bean class="o.s.i.xml.transformer.ResultToDocumentTransformer"/>
</constructor-arg>
</bean>
Starting with Spring Integration 3.0, you can specify the transformer factory class name by using a constructor argument.
You can do so by using the transformer-factory-class
attribute when you use the namespace.
Using ResultTransformer
Implementations
Both the MarshallingTransformer
and the XsltPayloadTransformer
let you specify a ResultTransformer
.
Thus, if the marshalling or XSLT transformation returns a Result
, you have the option to also use a ResultTransformer
to transform the Result
into another format.
Spring Integration provides two concrete ResultTransformer
implementations:
By default, the MarshallingTransformer
always returns a Result
.
By specifying a ResultTransformer
, you can customize the type of payload returned.
The behavior is slightly more complex for the XsltPayloadTransformer
.
By default, if the input payload is an instance of String
or Document
the resultTransformer
property is ignored.
However, if the input payload is a Source
or any other type, the resultTransformer
property is applied.
Additionally, you can set the alwaysUseResultFactory
property to true
, which also causes the specified resultTransformer
to be used.
For more information and examples, see Namespace Configuration and Result Transformers.
38.2.2. Namespace Support for XML Transformers
Namespace support for all XML transformers is provided in the Spring Integration XML namespace, a template for which was shown earlier.
The namespace support for transformers creates an instance of either EventDrivenConsumer
or PollingConsumer
, according to the type of the provided input channel.
The namespace support is designed to reduce the amount of XML configuration by allowing the creation of an endpoint and transformer that use one element.
Using an UnmarshallingTransformer
The namespace support for the UnmarshallingTransformer
is shown below.
Since the namespace create an endpoint instance rather than a transformer, you can nest a poller within the element to control the polling of the input channel.
The following example shows how to do so:
<int-xml:unmarshalling-transformer id="defaultUnmarshaller"
input-channel="input" output-channel="output"
unmarshaller="unmarshaller"/>
<int-xml:unmarshalling-transformer id="unmarshallerWithPoller"
input-channel="input" output-channel="output"
unmarshaller="unmarshaller">
<int:poller fixed-rate="2000"/>
<int-xml:unmarshalling-transformer/>
Using a MarshallingTransformer
The namespace support for the marshalling transformer requires an input-channel
, an output-channel
, and a reference to a marshaller
.
You can use the optional result-type
attribute to control the type of result created.
Valid values are StringResult
or DomResult
(the default).
The following example configures a marshalling transformer:
<int-xml:marshalling-transformer
input-channel="marshallingTransformerStringResultFactory"
output-channel="output"
marshaller="marshaller"
result-type="StringResult" />
<int-xml:marshalling-transformer
input-channel="marshallingTransformerWithResultTransformer"
output-channel="output"
marshaller="marshaller"
result-transformer="resultTransformer" />
<bean id="resultTransformer" class="o.s.i.xml.transformer.ResultToStringTransformer"/>
Where the provided result types do not suffice, you can provide a reference to a custom implementation of ResultFactory
as an alternative to setting the result-type
attribute by using the result-factory
attribute.
The result-type
and result-factory
attributes are mutually exclusive.
Internally, the StringResult and DomResult result types are represented by the ResultFactory implementations: StringResultFactory and DomResultFactory respectively.
|
Using an XsltPayloadTransformer
Namespace support for the XsltPayloadTransformer
lets you either pass in a Resource
(in order to create the Templates
instance) or pass in a pre-created Templates
instance as a reference.
As with the marshalling transformer, you can control the type of the result output by specifying either the result-factory
or the result-type
attribute.
When you need to convert result before sending, you can use a result-transformer
attribute to reference an implementation of ResultTransformer
.
If you specify the result-factory or the result-type attribute, the alwaysUseResultFactory property on the underlying XsltPayloadTransformer is set to true by the XsltPayloadTransformerParser .
|
The following example configures two XSLT transformers:
<int-xml:xslt-transformer id="xsltTransformerWithResource"
input-channel="withResourceIn" output-channel="output"
xsl-resource="org/springframework/integration/xml/config/test.xsl"/>
<int-xml:xslt-transformer id="xsltTransformerWithTemplatesAndResultTransformer"
input-channel="withTemplatesAndResultTransformerIn" output-channel="output"
xsl-templates="templates"
result-transformer="resultTransformer"/>
You may need to have access to Message
data, such as the Message
headers, in order to assist with transformation.
For example, you may need to get access to certain Message
headers and pass them on as parameters to a transformer (for example, transformer.setParameter(..)
).
Spring Integration provides two convenient ways to accomplish this, as the following example shows:
<int-xml:xslt-transformer id="paramHeadersCombo"
input-channel="paramHeadersComboChannel" output-channel="output"
xsl-resource="classpath:transformer.xslt"
xslt-param-headers="testP*, *foo, bar, baz">
<int-xml:xslt-param name="helloParameter" value="hello"/>
<int-xml:xslt-param name="firstName" expression="headers.fname"/>
</int-xml:xslt-transformer>
If message header names match one-to-one to parameter names, you can use the xslt-param-headers
attribute.
In it, you can use wildcards for simple pattern matching.
It supports the following simple pattern styles: xxx*
, xxx
, *xxx
, and xxx*yyy
.
You can also configure individual XSLT parameters by using the <xslt-param/>
element.
On that element, you can set the expression
attribute or the value
attribute.
The expression
attribute should be any valid SpEL expression with the Message
being the root object of the expression evaluation context.
The value
attribute (as with any value
in Spring beans) lets you specify simple scalar values.
You can also use property placeholders (such as ${some.value}
).
So, with the expression
and value
attributes, you can map XSLT parameters to any accessible part of the Message
as well as any literal value.
Starting with Spring Integration 3.0, you can now specify the transformer factory class name by setting the transformer-factory-class
attribute.
38.2.3. Namespace Configuration and Result Transformers
We cover using result transformers in Using ResultTransformer
Implementations.
The examples in this section use XML namespace configuration to illustrates several special use cases.
First, we define the ResultTransformer
, as the following example shows:
<beans:bean id="resultToDoc" class="o.s.i.xml.transformer.ResultToDocumentTransformer"/>
This ResultTransformer
accepts either a StringResult
or a DOMResult
as input and converts the input into a Document
.
Now we can declare the transformer, as follows:
<int-xml:xslt-transformer input-channel="in" output-channel="fahrenheitChannel"
xsl-resource="classpath:noop.xslt" result-transformer="resultToDoc"/>
If the incoming message’s payload is of type Source
, then, as a first step, the Result
is determined by using the ResultFactory
.
As we did not specify a ResultFactory
, the default DomResultFactory
is used, meaning that the transformation yields a DomResult
.
However, as we specified a ResultTransformer
, it is used and the resulting Message
payload is of type Document
.
The specified ResultTransformer is ignored with String or Document payloads.
If the incoming message’s payload is of type String , the payload after the XSLT transformation is a String .
Similarly, if the incoming message’s payload is of type Document , the payload after the XSLT transformation is a`Document`.
|
If the message payload is not a Source
, a String
, or a Document
, as a fallback option, we try to create a`Source` by using the default SourceFactory
.
As we did not specify a SourceFactory
explicitly by using the source-factory
attribute, the default DomSourceFactory
is used.
If successful, the XSLT transformation is executed as if the payload was of type Source
, as described in the previous paragraphs.
The DomSourceFactory supports the creation of a DOMSource from a Document , a File , or a String payload.
|
The next transformer declaration adds a result-type
attribute that uses StringResult
as its value.
The result-type
is internally represented by the StringResultFactory
.
Thus, you could have also added a reference to a StringResultFactory
, by using the result-factory
attribute, which would have been the same.
The following example shows that transformer declaration:
<int-xml:xslt-transformer input-channel="in" output-channel="fahrenheitChannel"
xsl-resource="classpath:noop.xslt" result-transformer="resultToDoc"
result-type="StringResult"/>
Because we use a ResultFactory
, the alwaysUseResultFactory
property of the XsltPayloadTransformer
class is implicitly set to true
.
Consequently, the referenced ResultToDocumentTransformer
is used.
Therefore, if you transform a payload of type String
, the resulting payload is of type Document
.
XsltPayloadTransformer
and <xsl:output method="text"/>
<xsl:output method="text"/>
tells the XSLT template to produce only text content from the input source.
In this particular case, we have no reason to use a DomResult
.
Therefore, the XsltPayloadTransformer
defaults to StringResult
if the output property called method
of the underlying javax.xml.transform.Transformer
returns text
.
This coercion is performed independently from the inbound payload type.
This behavior is available only you set the if the result-type
attribute or the result-factory
attribute for the <int-xml:xslt-transformer>
component.
38.3. Transforming XML Messages with XPath
When it comes to message transformation, XPath is a great way to transform messages that have XML payloads.
You can do so by defining XPath transformers with the <xpath-transformer/>
element.
38.3.1. Simple XPath Transformation
Consider following transformer configuration:
<int-xml:xpath-transformer input-channel="inputChannel" output-channel="outputChannel"
xpath-expression="/person/@name" />
Also consider the following Message
:
Message<?> message =
MessageBuilder.withPayload("<person name='John Doe' age='42' married='true'/>").build();
After sending this message to the 'inputChannel', the XPath transformer configured earlier transforms this XML Message to a simple Message
with a payload of 'John Doe', all based on the simple XPath Expression specified in the xpath-expression
attribute.

Valid return types are defined in javax.xml.xpath.XPathConstants
and follow the conversion rules specified by the javax.xml.xpath.XPath
interface.
The following constants are defined by the XPathConstants
class: BOOLEAN
, DOM_OBJECT_MODEL
, NODE
, NODESET
, NUMBER
, and STRING
.
You can configure the desired type by using the evaluation-type
attribute of the <xpath-transformer/>
element, as the following example shows (twice):
<int-xml:xpath-transformer input-channel="numberInput" xpath-expression="/person/@age"
evaluation-type="NUMBER_RESULT" output-channel="output"/>
<int-xml:xpath-transformer input-channel="booleanInput"
xpath-expression="/person/@married = 'true'"
evaluation-type="BOOLEAN_RESULT" output-channel="output"/>
38.3.2. Node Mappers
If you need to provide custom mapping for the node extracted by the XPath expression, you can provide a reference to the implementation of the org.springframework.xml.xpath.NodeMapper
(an interface used by XPathOperations
implementations for mapping Node
objects on a per-node basis).
To provide a reference to a NodeMapper
, you can use the node-mapper
attribute, as the following example shows:
<int-xml:xpath-transformer input-channel="nodeMapperInput" xpath-expression="/person/@age"
node-mapper="testNodeMapper" output-channel="output"/>
The following example shows a NodeMapper
implementation that works with the preceding example:
class TestNodeMapper implements NodeMapper {
public Object mapNode(Node node, int nodeNum) throws DOMException {
return node.getTextContent() + "-mapped";
}
}
38.3.3. XML Payload Converter
You can also use an implementation of the org.springframework.integration.xml.XmlPayloadConverter
to provide more granular transformation.
The following example shows how to define one:
<int-xml:xpath-transformer input-channel="customConverterInput"
output-channel="output" xpath-expression="/test/@type"
converter="testXmlPayloadConverter" />
The following example shows an XmlPayloadConverter
implementation that works with the preceding example:
class TestXmlPayloadConverter implements XmlPayloadConverter {
public Source convertToSource(Object object) {
throw new UnsupportedOperationException();
}
//
public Node convertToNode(Object object) {
try {
return DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(
new InputSource(new StringReader("<test type='custom'/>")));
}
catch (Exception e) {
throw new IllegalStateException(e);
}
}
//
public Document convertToDocument(Object object) {
throw new UnsupportedOperationException();
}
}
If you do not provide this reference, the DefaultXmlPayloadConverter
is used.
It should suffice in most cases, because it can convert from Node
, Document
, Source
, File
, String
, InputStream
, and byte[]
payloads.
If you need to extend beyond the capabilities of that default implementation, an upstream Transformer
is probably a better option than providing a reference to a custom implementation of this strategy here.
38.4. Splitting XML Messages
XPathMessageSplitter
supports messages with either String
or Document
payloads.
The splitter uses the provided XPath expression to split the payload into a number of nodes.
By default, this results in each Node
instance becoming the payload of a new message.
When each message should be a Document
, you can set the createDocuments
flag.
Where a String
payload is passed in, the payload is converted and then split before being converted back to a number of String
messages.
The XPath splitter implements MessageHandler
and should therefore be configured in conjunction with an appropriate endpoint (see the namespace support example after the following example for a simpler configuration alternative).
The following example configures a bean that uses an XPathMessageSplitter
:
<bean id="splittingEndpoint"
class="org.springframework.integration.endpoint.EventDrivenConsumer">
<constructor-arg ref="orderChannel" />
<constructor-arg>
<bean class="org.springframework.integration.xml.splitter.XPathMessageSplitter">
<constructor-arg value="/order/items" />
<property name="documentBuilder" ref="customisedDocumentBuilder" />
<property name="outputChannel" ref="orderItemsChannel" />
</bean>
</constructor-arg>
</bean>
XPath splitter namespace support lets you create a message endpoint with an input channel and output channel, as the following example shows:
<!-- Split the order into items and create a new message for each item node -->
<int-xml:xpath-splitter id="orderItemSplitter"
input-channel="orderChannel"
output-channel="orderItemsChannel">
<int-xml:xpath-expression expression="/order/items"/>
</int-xml:xpath-splitter>
<!-- Split the order into items, create a new document for each item-->
<int-xml:xpath-splitter id="orderItemDocumentSplitter"
input-channel="orderChannel"
output-channel="orderItemsChannel"
create-documents="true">
<int-xml:xpath-expression expression="/order/items"/>
<int:poller fixed-rate="2000"/>
</int-xml:xpath-splitter>
Starting with version 4.2, the XPathMessageSplitter
exposes the outputProperties
(such as OutputKeys.OMIT_XML_DECLARATION
) property for an javax.xml.transform.Transformer
instance when a request payload
is not of type org.w3c.dom.Node
.
The following example defines a property and uses it with the output-properties
property:
<util:properties id="outputProperties">
<beans:prop key="#{T (javax.xml.transform.OutputKeys).OMIT_XML_DECLARATION}">yes</beans:prop>
</util:properties>
<xpath-splitter input-channel="input"
output-properties="outputProperties">
<xpath-expression expression="/orders/order"/>
</xpath-splitter>
Starting with version 4.2
, the XPathMessageSplitter
exposes an iterator
option as a boolean
flag (defaults to true
).
This allows the “streaming” of split nodes in the downstream flow.
With the iterator
mode set to true
, each node is transformed while iterating.
When false
, all entries are first transformed, before the split nodes start being sent to the output channel.
(You can think of the difference as “transform, send, transform, send” versus “transform, transform, send, send”.)
See Splitter for more information.
38.5. Routing XML Messages with XPath
Similar to SpEL-based routers, Spring Integration provides support for routing messages based on XPath expressions, which lets you create a message endpoint with an input channel but no output channel. Instead, one or more output channels are determined dynamically. The following example shows how to create such a router:
<int-xml:xpath-router id="orderTypeRouter" input-channel="orderChannel">
<int-xml:xpath-expression expression="/order/type"/>
</int-xml:xpath-router>
| For an overview of attributes that are common among Routers, see Common Router Parameters. |
Internally, XPath expressions are evaluated as type NODESET
and converted to a List<String>
that represents channel names.
Typically, such a list contains a single channel name.
However, based on the results of an XPath Expression, the XPath router can also take on the characteristics of a recipient list router if the XPath expression returns more than one value.
In that case, the List<String>
contains more than one channel name.
Consequently, messages are sent to all the channels in the list.
Thus, assuming that the XML file passed to the following router configuration contains many responder
sub-elements that represent channel names, the message is sent to all of those channels:
<!-- route the order to all responders-->
<int-xml:xpath-router id="responderRouter" input-channel="orderChannel">
<int-xml:xpath-expression expression="/request/responders"/>
</int-xml:xpath-router>
If the returned values do not represent the channel names directly, you can specify additional mapping parameters to map those returned values to actual channel names.
For example if the /request/responders
expression results in two values (responderA
and responderB
), but you do not want to couple the responder names to channel names, you can provide additional mapping configuration, such as the following:
<!-- route the order to all responders-->
<int-xml:xpath-router id="responderRouter" input-channel="orderChannel">
<int-xml:xpath-expression expression="/request/responders"/>
<int-xml:mapping value="responderA" channel="channelA"/>
<int-xml:mapping value="responderB" channel="channelB"/>
</int-xml:xpath-router>
As already mentioned, the default evaluation type for XPath expressions is NODESET
, which is converted to a List<String>
of channel names, which handles single channel scenarios as well as multiple channel scenarios.
Nonetheless, certain XPath expressions may evaluate as type String
from the very beginning.
Consider, for example, the following XPath Expression:
name(./node())
This expression returns the name of the root node.
If the default evaluation type NODESET
is being used, it results in an exception.
For these scenarios, you can use the evaluate-as-string
attribute, which lets you manage the evaluation type.
It is FALSE
by default.
However, if you set it to TRUE
, the String
evaluation type is used.
|
XPath 1.0 specifies 4 data types:
When the XPath Router evaluates expressions by using the optional For further information, see: |
For example, if we want to route based on the name of the root node, we can use the following configuration:
<int-xml:xpath-router id="xpathRouterAsString"
input-channel="xpathStringChannel"
evaluate-as-string="true">
<int-xml:xpath-expression expression="name(./node())"/>
</int-xml:xpath-router>
38.5.1. XML Payload Converter
For XPath Routers, you can also specify the Converter to use when converting payloads prior to XPath evaluation.
As such, the XPath Router supports custom implementations of the XmlPayloadConverter
strategy, and when configuring an xpath-router
element in XML, a reference to such an implementation may be provided via the converter
attribute.
If this reference is not explicitly provided, the DefaultXmlPayloadConverter
is used.
It should be sufficient in most cases, since it can convert from Node, Document, Source, File, and String typed payloads.
If you need to extend beyond the capabilities of that default implementation, then an upstream Transformer is generally a better option in most cases, rather than providing a reference to a custom implementation of this strategy here.
38.6. XPath Header Enricher
The XPath header enricher defines a header enricher message transformer that evaluates an XPath expression against the message payload and inserts the result of the evaluation into a message header.
The following listing shows all the available configuration parameters:
<int-xml:xpath-header-enricher default-overwrite="true" (1)
id="" (2)
input-channel="" (3)
output-channel="" (4)
should-skip-nulls="true"> (5)
<int:poller></int:poller> (6)
<int-xml:header name="" (7)
evaluation-type="STRING_RESULT" (8)
header-type="int" (9)
overwrite="true" (10)
xpath-expression="" (11)
xpath-expression-ref=""/> (12)
</int-xml:xpath-header-enricher>
| 1 | Specifies the default boolean value for whether to overwrite existing header values. This takes effect only for child elements that do not provide their own 'overwrite' attribute. If you do not set the 'default- overwrite' attribute, the specified header values do not overwrite any existing ones with the same header names. Optional. |
| 2 | ID for the underlying bean definition. Optional. |
| 3 | The receiving message channel of this endpoint. Optional. |
| 4 | Channel to which enriched messages are sent. Optional. |
| 5 | Specifies whether null values, such as might be returned from an expression evaluation, should be skipped.
The default value is true .
If a null value should trigger removal of the corresponding header, set this to false .
Optional. |
| 6 | A poller to use with the header enricher. Optional. |
| 7 | The name of the header to be enriched. Mandatory. |
| 8 | The result type expected from the XPath evaluation.
If you did not set a header-type attribute, this is the type of the header value.
The following values are allowed: BOOLEAN_RESULT , STRING_RESULT , NUMBER_RESULT , NODE_RESULT , and NODE_LIST_RESULT .
If not set, it defaults internally to XPathEvaluationType.STRING_RESULT .
Optional. |
| 9 | The fully qualified class name for the header value type.
The result of the XPath evaluation is converted to this type by ConversionService .
This allows, for example, a NUMBER_RESULT (a double) to be converted to an Integer .
The type can be declared as a primitive (such as int ), but the result is always the equivalent wrapper class (such as Integer ).
The same integration ConversionService discussed in Payload Type Conversion is used for the conversion, so conversion to custom types is supported by adding a custom converter to the service.
Optional. |
| 10 | Boolean value to indicate whether this header value should overwrite an existing header value for the same name if already present on the input Message . |
| 11 | The XPath expression as a String .
You must set either this attribute or xpath-expression-ref , but not both. |
| 12 | The XPath expression reference.
You must set either this attribute or xpath-expression , but not both. |
38.7. Using the XPath Filter
This component defines an XPath-based message filter.
Internally, this components uses a MessageFilter
that wraps an instance of AbstractXPathMessageSelector
.
| See Filter for further details. |
to use the XPath filter you must, at a minimum, provide an XPath expression either by declaring the xpath-expression
element or by referencing an XPath Expression in the xpath-expression-ref
attribute.
If the provided XPath expression evaluates to a boolean
value, no further configuration parameters are necessary.
However, if the XPath expression evaluates to a String
, you should set the match-value
attribute, against which the evaluation result is matched.
match-type
has three options:
-
exact
: Correspond toequals
onjava.lang.String
. The underlying implementation uses aStringValueTestXPathMessageSelector
-
case-insensitive
: Correspond toequals-ignore-case
onjava.lang.String
. The underlying implementation uses aStringValueTestXPathMessageSelector
-
regex
: Matches operations onejava.lang.String
. The underlying implementation uses aRegexTestXPathMessageSelector
When providing a 'match-type' value of 'regex', the value provided with the match-value
attribute must be a valid regular expression.
The following example shows all the available attributes for the xpath-filter
element:
<int-xml:xpath-filter discard-channel="" (1)
id="" (2)
input-channel="" (3)
match-type="exact" (4)
match-value="" (5)
output-channel="" (6)
throw-exception-on-rejection="false" (7)
xpath-expression-ref=""> (8)
<int-xml:xpath-expression ... /> (9)
<int:poller ... /> (10)
</int-xml:xpath-filter>
| 1 | Message channel where you want rejected messages to be sent. Optional. |
| 2 | ID for the underlying bean definition. Optional. |
| 3 | The receiving message channel of this endpoint. Optional. |
| 4 | Type of match to apply between the XPath evaluation result and the match-value .
The default is exact .
Optional. |
| 5 | String value to be matched against the XPath evaluation result. If you do not set this attribute, the XPath evaluation must produce a boolean result. Optional. |
| 6 | The channel to which messages that matched the filter criteria are dispatched. Optional. |
| 7 | By default, this property is set to false and rejected messages (messages that did not match the filter criteria) are silently dropped.
However, if set to true , message rejection results in an error condition and an exception being propagated upstream to the caller.
Optional. |
| 8 | Reference to an XPath expression instance to evaluate. |
| 9 | This child element sets the XPath expression to be evaluated.
If you do not include this element, you must set the xpath-expression-ref attribute.
Also, you can include only one xpath-expression element. |
| 10 | A poller to use with the XPath filter. Optional. |
38.8. #xpath SpEL Function
Spring Integration, since version 3.0, provides the built-in #xpath
SpEL function, which invokes the XPathUtils.evaluate(…)
static method.
This method delegates to an org.springframework.xml.xpath.XPathExpression
.
The following listing shows some usage examples:
<transformer expression="#xpath(payload, '/name')"/>
<filter expression="#xpath(payload, headers.xpath, 'boolean')"/>
<splitter expression="#xpath(payload, '//book', 'document_list')"/>
<router expression="#xpath(payload, '/person/@age', 'number')">
<mapping channel="output1" value="16"/>
<mapping channel="output2" value="45"/>
</router>
The #xpath()
also supports a third optional parameter for converting the result of the XPath evaluation.
It can be one of the String constants (string
, boolean
, number
, node
, node_list
and document_list
) or an org.springframework.xml.xpath.NodeMapper
instance.
By default, the #xpath
SpEL function returns a String
representation of the XPath evaluation.
To enable the #xpath SpEL function, you can add the spring-integration-xml.jar to the classpath.
You need no declare any components from the Spring Integration XML Namespace.
|
For more information, see "`Spring Expression Language (SpEL).
38.9. XML Validating Filter
The XML Validating Filter lets you validate incoming messages against provided schema instances. The following schema types are supported:
-
xml-schema (www.w3.org/2001/XMLSchema)
-
relax-ng (relaxng.org)
Messages that fail validation can either be silently dropped or be forwarded to a definable discard-channel
.
Furthermore, you can configure this filter to throw an Exception
in case validation fails.
The following listing shows all the available configuration parameters:
<int-xml:validating-filter discard-channel="" (1)
id="" (2)
input-channel="" (3)
output-channel="" (4)
schema-location="" (5)
schema-type="xml-schema" (6)
throw-exception-on-rejection="false" (7)
xml-converter="" (8)
xml-validator=""> (9)
<int:poller .../> (10)
</int-xml:validating-filter>
| 1 | Message channel where you want rejected messages to be sent. Optional. |
| 2 | ID for the underlying bean definition. Optional. |
| 3 | The receiving message channel of this endpoint. Optional. |
| 4 | Message channel where you want accepted messages to be sent. Optional. |
| 5 | Sets the location of the schema to validate the message’s payload against.
Internally uses the org.springframework.core.io.Resource interface.
You can set this attribute or the xml-validator attribute but not both.
Optional. |
| 6 | Sets the schema type.
Can be either xml-schema or relax-ng .
Optional.
If not set, it defaults to xml-schema , which internally translates to org.springframework.xml.validation.XmlValidatorFactory#SCHEMA_W3C_XML . |
| 7 | If true , a MessageRejectedException is thrown if validation fails for the provided Message’s payload.
Defaults to false if not set.
Optional. |
| 8 | Reference to a custom org.springframework.integration.xml.XmlPayloadConverter strategy.
Optional. |
| 9 | Reference to a custom sorg.springframework.xml.validation.XmlValidator strategy.
You can set this attribute or the schema-location attribute but not both.
Optional. |
| 10 | A poller to use with the XPath filter. Optional. |
39. XMPP Support
Spring Integration provides channel adapters for XMPP. XMPP stands for “Extensible Messaging and Presence Protocol”.
XMPP describes a way for multiple agents to communicate with each other in a distributed system. The canonical use case is to send and receive chat messages, though XMPP can be (and is) used for other kinds of applications. XMPP describes a network of actors. Within that network, actors may address each other directly and broadcast status changes (such as “presence”).
XMPP provides the messaging fabric that underlies some of the biggest instant messaging networks in the world, including Google Talk (GTalk, which is also available from within GMail) and Facebook Chat. Many good open-source XMPP servers are available. Two popular implementations are Openfire and ejabberd.
Spring integration provides support for XMPP by providing XMPP adapters, which support sending and receiving both XMPP chat messages and presence changes from other entries in a client’s roster.
You need to include this dependency into your project:
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-xmpp</artifactId>
<version>5.1.5.RELEASE</version>
</dependency>
compile "org.springframework.integration:spring-integration-xmpp:5.1.5.RELEASE"
As with other adapters, the XMPP adapters come with support for a convenient namespace-based configuration. To configure the XMPP namespace, include the following elements in the headers of your XML configuration file:
xmlns:int-xmpp="http://www.springframework.org/schema/integration/xmpp"
xsi:schemaLocation="http://www.springframework.org/schema/integration/xmpp
https://www.springframework.org/schema/integration/xmpp/spring-integration-xmpp.xsd"
39.1. XMPP Connection
Before using inbound or outbound XMPP adapters to participate in the XMPP network, an actor must establish its XMPP connection.
All XMPP adapters connected to a particular account can share this connection object.
Typically this requires (at a minimum) user
, password
, and host
.
To create a basic XMPP connection, you can use the convenience of the namespace, as the following example shows:
<int-xmpp:xmpp-connection
id="myConnection"
user="user"
password="password"
host="host"
port="port"
resource="theNameOfTheResource"
subscription-mode="accept_all"/>
For added convenience, you can rely on the default naming convention and omit the id attribute.
The default name (xmppConnection ) is used for this connection bean.
|
If the XMPP connection goes stale, reconnection attempts are made with an automatic login as long as the previous connection state was logged (authenticated).
We also register a ConnectionListener
, which logs connection events if the DEBUG
logging level is enabled.
The subscription-mode
attribute initiates the roster listener to deal with incoming subscriptions from other users.
This functionality is not always available for the target XMPP servers.
For example, Google Cloud Messaging (GCM) and Firebase Cloud Messaging (FCM) fully disable it.
To switch off the roster listener for subscriptions, you can configure it with an empty string when using XML configuration (subscription-mode=""
) or with XmppConnectionFactoryBean.setSubscriptionMode(null)
when using Java Configuration.
Doing so disables the roster at the login phase as well.
See Roster.setRosterLoadedAtLogin(boolean)
for more information.
39.2. XMPP Messages
Spring Integration provides support for sending and receiving XMPP messages. For receiving them, it offers an inbound message channel adapter. For sending them, it offers an outbound message channel adapter.
39.2.1. Inbound Message Channel Adapter
The Spring Integration adapters support receiving chat messages from other users in the system.
To do so, the inbound message channel adapter “logs in” as a user on your behalf and receives the messages sent to that user.
Those messages are then forwarded to your Spring Integration client.
The inbound-channel-adapter
element provides Configuration support for the XMPP inbound message channel adapter.
The following example shows how to configure it:
<int-xmpp:inbound-channel-adapter id="xmppInboundAdapter"
channel="xmppInbound"
xmpp-connection="testConnection"
payload-expression="getExtension('google:mobile:data').json"
stanza-filter="stanzaFilter"
auto-startup="true"/>
Along with the usual attributes (for a message channel adapter), this adapter also requires a reference to an XMPP Connection.
The XMPP inbound adapter is event-driven and a Lifecycle
implementation.
When started, it registers a PacketListener
that listens for incoming XMPP chat messages.
It forwards any received messages to the underlying adapter, which converts them to Spring Integration messages and sends them to the specified channel
.
When stopped, it unregisters the PacketListener
.
Starting with version 4.3, the ChatMessageListeningEndpoint
(and its <int-xmpp:inbound-channel-adapter>
) supports the injection of a org.jivesoftware.smack.filter.StanzaFilter
to be registered on the provided XMPPConnection
, together with an internal StanzaListener
implementation.
See the Javadoc for more information.
Version 4.3 introduced the payload-expression
attribute for the ChatMessageListeningEndpoint
.
The incoming org.jivesoftware.smack.packet.Message
represents a root object for the evaluation context.
This option is useful when you use XMPP extensions.
For example, for the GCM protocol we can extract the body by using the following expression:
payload-expression="getExtension('google:mobile:data').json"
The following example extracts the body for the XHTML protocol:
payload-expression="getExtension(T(org.jivesoftware.smackx.xhtmlim.packet.XHTMLExtension).NAMESPACE).bodies[0]"
To simplify access to the extension in the XMPP Message, the extension
variable is added into the EvaluationContext
.
Note that it is added when only one extension is present in the message.
The preceding examples that show the namespace
manipulations can be simplified to the following example:
payload-expression="#extension.json"
payload-expression="#extension.bodies[0]"
39.2.2. Outbound Message Channel Adapter
You can also send chat messages to other users on XMPP by using the outbound message channel adapter.
The outbound-channel-adapter
element provides configuration support for the XMPP outbound message channel adapter.
<int-xmpp:outbound-channel-adapter id="outboundEventAdapter"
channel="outboundEventChannel"
xmpp-connection="testConnection"/>
The adapter expects its input to be (at a minimum) a payload of type java.lang.String
and a header value for XmppHeaders.CHAT_TO
that specifies to which user the message should be sent.
To create a message, you can use Java code similar to the following:
Message<String> xmppOutboundMsg = MessageBuilder.withPayload("Hello, XMPP!" )
.setHeader(XmppHeaders.CHAT_TO, "userhandle")
.build();
You can also set the header by using the XMPP header-enricher support, as the following example shows:
<int-xmpp:header-enricher input-channel="input" output-channel="output">
<int-xmpp:chat-to<EMAIL_ADDRESS></int-xmpp:header-enricher>
Starting with version 4.3, the packet extension support has been added to the ChatMessageSendingMessageHandler
(the <int-xmpp:outbound-channel-adapter>
in XML configuration).
Along with the regular String
and org.jivesoftware.smack.packet.Message
payload, now you can send a message with a payload of org.jivesoftware.smack.packet.ExtensionElement
(which is populated to the org.jivesoftware.smack.packet.Message.addExtension()
) instead of setBody()
.
For convenience, we added an extension-provider
option for the ChatMessageSendingMessageHandler
.
It lets you inject org.jivesoftware.smack.provider.ExtensionElementProvider
, which builds an ExtensionElement
against the payload at runtime.
For this case, the payload must be a string in JSON or XML format, depending of the XEP protocol.
39.3. XMPP Presence
XMPP also supports broadcasting state. You can use this ability to let people who have you on their roster see your state changes. This happens all the time with your IM clients. You change your away status and set an away message, and everybody who has you on their roster sees your icon or username change to reflect this new state and might see your new “away” message. If you would like to receive notifications or notify others of state changes, you can use Spring Integration’s “presence” adapters.
39.3.1. Inbound Presence Message Channel Adapter
Spring Integration provides an inbound presence message channel adapter, which supports receiving presence events from other users in the system who are on your roster.
To do this, the adapter “logs in” as a user on your behalf, registers a RosterListener
, and forwards received presence update events as messages to the channel identified by the channel
attribute.
The payload of the message is a org.jivesoftware.smack.packet.Presence
object (see www.igniterealtime.org/builds/smack/docs/latest/javadoc/org/jivesoftware/smack/packet/Presence.html).
The presence-inbound-channel-adapter
element provides configuration support for the XMPP inbound presence message channel adapter.
The following example configures an inbound presence message channel adapter:
<int-xmpp:presence-inbound-channel-adapter channel="outChannel"
xmpp-connection="testConnection" auto-startup="false"/>
Along with the usual attributes, this adapter requires a reference to an XMPP Connection.
This adapter is event-driven and a Lifecycle
implementation.
It registers a RosterListener
when started and unregisters that RosterListener
when stopped.
39.3.2. Outbound Presence Message Channel Adapter
Spring Integration also supports sending presence events to be seen by other users in the network who happen to have you on their roster.
When you send a message to the outbound presence message channel adapter, it extracts the payload (which is expected to be of type org.jivesoftware.smack.packet.Presence
) and sends it to the XMPP Connection, thus advertising your presence events to the rest of the network.
The presence-outbound-channel-adapter
element provides configuration support for the XMPP outbound presence message channel adapter.
The following example shows how to configure an outbound presence message channel adapter:
<int-xmpp:presence-outbound-channel-adapter id="eventOutboundPresenceChannel"
xmpp-connection="testConnection"/>
It can also be a polling consumer (if it receives messages from a pollable channel) in which case you would need to register a poller. The following example shows how to do so:
<int-xmpp:presence-outbound-channel-adapter id="pollingOutboundPresenceAdapter"
xmpp-connection="testConnection"
channel="pollingChannel">
<int:poller fixed-rate="1000" max-messages-per-poll="1"/>
</int-xmpp:presence-outbound-channel-adapter>
Like its inbound counterpart, it requires a reference to an XMPP Connection.
If you rely on the default naming convention for an XMPP Connection bean (described earlier) and you have only one XMPP Connection bean configured in your application context, you can omit the xmpp-connection attribute.
In that case, the bean with named xmppConnection is located and injected into the adapter.
|
39.4. Advanced Configuration
Spring Integration’s XMPP support is based on the Smack 4.0 API (www.igniterealtime.org/projects/smack/), which allows more complex configuration of the XMPP Connection object.
As stated earlier, the xmpp-connection
namespace support is designed to simplify basic connection configuration and supports only a few common configuration attributes.
However, the org.jivesoftware.smack.ConnectionConfiguration
object defines about 20 attributes, and adding namespace support for all of them offers no real value.
So, for more complex connection configurations, you can configure an instance of our XmppConnectionFactoryBean
as a regular bean and inject a org.jivesoftware.smack.ConnectionConfiguration
as a constructor argument to that FactoryBean
.
You can specify every property you need directly on that ConnectionConfiguration
instance.
(A bean definition with the 'p' namespace would work well.)
This way, you can directly set SSL (or any other attributes).
The following example shows how to do so:
<bean id="xmppConnection" class="o.s.i.xmpp.XmppConnectionFactoryBean">
<constructor-arg>
<bean class="org.jivesoftware.smack.ConnectionConfiguration">
<constructor-arg value="myServiceName"/>
<property name="socketFactory" ref="..."/>
</bean>
</constructor-arg>
</bean>
<int:channel id="outboundEventChannel"/>
<int-xmpp:outbound-channel-adapter id="outboundEventAdapter"
channel="outboundEventChannel"
xmpp-connection="xmppConnection"/>
The Smack API also offers static initializers, which can be helpful.
For more complex cases (such as registering a SASL mechanism), you may need to execute certain static initializers.
One of those static initializers is SASLAuthentication
, which lets you register supported SASL mechanisms.
For that level of complexity, we recommend using Spring Java configuration for the XMPP connection configuration.
That way, you can configure the entire component through Java code and execute all other necessary Java code, including static initializers, at the appropriate time.
The following exampe shows how to configure an XMPP connection with an SASL (Simple Authentication and Security Layer) in Java:
@Configuration
public class CustomConnectionConfiguration {
@Bean
public XMPPConnection xmppConnection() {
SASLAuthentication.supportSASLMechanism("EXTERNAL", 0); // static initializer
ConnectionConfiguration config = new ConnectionConfiguration("localhost", 5223);
config.setTrustorePath("path_to_truststore.jks");
config.setSecurityEnabled(true);
config.setSocketFactory(SSLSocketFactory.getDefault());
return new XMPPConnection(config);
}
}
For more information on using Java for application context configuration, see the following section in the Spring Reference Manual.
39.5. XMPP Message Headers
The Spring Integration XMPP Adapters automatically map standard XMPP properties.
By default, these properties are copied to and from Spring Integration MessageHeaders
by using
DefaultXmppHeaderMapper
.
Any user-defined headers are not copied to or from an XMPP Message, unless explicitly specified by the requestHeaderNames
or replyHeaderNames
properties of the DefaultXmppHeaderMapper
.
| When mapping user-defined headers, the values can also contain simple wildcard patterns (such "thing*" or "*thing"). |
Starting with version 4.1, AbstractHeaderMapper
(a superclass of DefaultXmppHeaderMapper
) lets you configure the NON_STANDARD_HEADERS
token for the requestHeaderNames
property (in addition to STANDARD_REQUEST_HEADERS
), to map all user-defined headers.
The org.springframework.xmpp.XmppHeaders
class identifies the default headers to be used by the DefaultXmppHeaderMapper
:
-
xmpp_from
-
xmpp_subject
-
xmpp_thread
-
xmpp_to
-
xmpp_type
Starting with version 4.3, you can negate patterns in the header mappings by preceding the pattern with !
.
Negated patterns get priority, so a list such as STANDARD_REQUEST_HEADERS,thing1,thing*,!thing2,!thing3,qux,!thing1
does not map thing1
, thing2
,or thing3
.
That list does map the standard headers plus thing4
and qux
.
If you have a user-defined header that begins with ! that you do wish to map, can escape it with \ thus: STANDARD_REQUEST_HEADERS,\!myBangHeader .
In that example, the standard request headers and !myBangHeader are mapped.
|
39.6. XMPP Extensions
Extensions put the “Extensible” in the “Extensible Messaging and Presence Protocol”.
XMPP is based around XML, a data format that supports a concept known as namespacing. Through namespacing, you can add bits to XMPP that are not defined in the original specifications. The XMPP specification deliberately describes only a set of core features:
-
How a client connects to a server
-
Encryption (SSL/TLS)
-
Authentication
-
How servers can communicate with each other to relay messages
-
A few other basic building blocks
Once you have implemented this, you have an XMPP client and can send any kind of data you like. However, you may need to do more than the basics. For example, you might need to include formatting (bold, italic, and so on) in a message, which is not defined in the core XMPP specification. Well, you can make up a way to do that, but, unless everyone else does it the same way you do, no other software can interpret it (they ignore namespaces they cannot understand).
To solve that problem, the XMPP Standards Foundation (XSF) publishes a series of extra documents, known as XMPP Enhancement Proposals (XEPs). In general, each XEP describes a particular activity (from message formatting to file transfers, multi-user chats, and many more). They also provide a standard format for everyone to use for that activity.
The Smack API provides many XEP implementations with its extensions
and experimental
projects.
Starting with Spring Integration version 4.3, you can use any XEP with the existing XMPP channel adapters.
To be able to process XEPs or any other custom XMPP extensions, you must provide the Smack’s ProviderManager
pre-configuration.
You can do so with static
Java code, as the following example shows:
ProviderManager.addIQProvider("element", "namespace", new MyIQProvider());
ProviderManager.addExtensionProvider("element", "namespace", new MyExtProvider());
You can also use a .providers
configuration file in the specific instance and access it with a JVM argument, as the following example shows:
-Dsmack.provider.file=file:///c:/my/provider/mycustom.providers
The mycustom.providers
file might be as follows:
<?xml version="1.0"?>
<smackProviders>
<iqProvider>
<elementName>query</elementName>
<namespace>jabber:iq:time</namespace>
<className>org.jivesoftware.smack.packet.Time</className>
</iqProvider>
<iqProvider>
<elementName>query</elementName>
<namespace>https://jabber.org/protocol/disco#items</namespace>
<className>org.jivesoftware.smackx.provider.DiscoverItemsProvider</className>
</iqProvider>
<extensionProvider>
<elementName>subscription</elementName>
<namespace>https://jabber.org/protocol/pubsub</namespace>
<className>org.jivesoftware.smackx.pubsub.provider.SubscriptionProvider</className>
</extensionProvider>
</smackProviders>
For example, the most popular XMPP messaging extension is Google Cloud Messaging (GCM).
The Smack library provides org.jivesoftware.smackx.gcm.provider.GcmExtensionProvider
for that purposes.
By default, it registers that class with the smack-experimental
jar in the classpath by using the experimental.providers
resource, as the following Maven example shows:
<!-- GCM JSON payload -->
<extensionProvider>
<elementName>gcm</elementName>
<namespace>google:mobile:data</namespace>
<className>org.jivesoftware.smackx.gcm.provider.GcmExtensionProvider</className>
</extensionProvider>
Also, the GcmPacketExtension
lets the target messaging protocol parse incoming packets and build outgoing packets, as the following examples show:
GcmPacketExtension gcmExtension = (GcmPacketExtension) xmppMessage.getExtension(GcmPacketExtension.NAMESPACE);
String message = gcmExtension.getJson());
GcmPacketExtension packetExtension = new GcmPacketExtension(gcmJson);
Message smackMessage = new Message();
smackMessage.addExtension(packetExtension);
See Inbound Message Channel Adapter and Outbound Message Channel Adapter earlier in this chapter for more information.
40. Zookeeper Support
Version 4.2 added Zookeeper support to the framework in version 4.2, which consists of:
You need to include this dependency into your project:
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-zookeeper</artifactId>
<version>5.1.5.RELEASE</version>
</dependency>
compile "org.springframework.integration:spring-integration-zookeeper:5.1.5.RELEASE"
40.1. Zookeeper Metadata Store
You ca use the ZookeeperMetadataStore
where any MetadataStore
is needed, such as for persistent file list filters.
See Metadata Store for more information.
The following example configures a Zookeeper metadata store with XML:
<bean id="client" class="org.springframework.integration.zookeeper.config.CuratorFrameworkFactoryBean">
<constructor-arg value="${connect.string}" />
</bean>
<bean id="meta" class="org.springframework.integration.zookeeper.metadata.ZookeeperMetadataStore">
<constructor-arg ref="client" />
</bean>
The following example shows how to configure a Zookeeper metadata store with Java:
@Bean
public MetadataStore zkStore(CuratorFramework client) {
return new ZookeeperMetadataStore(client);
}
40.2. Zookeeper Lock Registry
The ZookeeperLockRegistry
can be used where any LockRegistry
is needed, such as when using an aggregator in a clustered environment with a shared MessageStore
.
A LockRegistry
is used to “look up” a lock based on a key (the aggregator uses correlationId
).
By default, locks in the ZookeeperLockRegistry
are maintained in zookeeper under the following path: /SpringIntegration-LockRegistry/
.
You can customize the path by providing an implementation of ZookeeperLockRegistry.KeyToPathStrategy
, as the following example shows:
public interface KeyToPathStrategy {
String pathFor(String key);
boolean bounded();
}
If the strategy returns true
from isBounded
, unused locks do not need to be harvested.
For unbounded strategies (such as the default), you need to periodically invoke expireUnusedOlderThan(long age)
to remove old unused locks from memory.
40.3. Zookeeper Leadership Event Handling
The following example uses XML to configure an application for leader election in Zookeeper:
<int-zk:leader-listener client="client" path="/siNamespace" role="cluster" />
client
is a reference to a CuratorFramework
bean.
A CuratorFrameworkFactoryBean
is available.
When a leader is elected, an OnGrantedEvent
is published for the role cluster
.
Any endpoints in that role are started.
When leadership is revoked, an OnRevokedEvent
is published for the role cluster
.
Any endpoints in that role are stopped.
See Endpoint Roles for more information.
You can use Java configuration to create an instance of the leader initiator, as the following example shows:
@Bean
public LeaderInitiatorFactoryBean leaderInitiator(CuratorFramework client) {
return new LeaderInitiatorFactoryBean()
.setClient(client)
.setPath("/siTest/")
.setRole("cluster");
}
Appendices
The appendices cover advanced topics and additional resources.
The last appendix covers the history of Spring Integration.
Appendix A: Spring Expression Language (SpEL)
You can configure many Spring Integration components by using expressions written in the Spring Expression Language.
In most cases, the #root
object is the Message
, which has two properties (headers
and payload
) that allow such expressions as payload
, payload.thing
, headers['my.header']
, and so on.
In some cases, additional variables are provided.
For example, <int-http:inbound-gateway/>
provides #requestParams
(parameters from the HTTP request) and #pathVariables
(values from path placeholders in the URI).
For all SpEL expressions, a BeanResolver
is available to enable references to any bean in the application context (for example, @myBean.foo(payload)
).
In addition, two PropertyAccessors
are available.
A MapAccessor
enables accessing values in a Map
by using a key and a ReflectivePropertyAccessor
, which allows access to fields and JavaBean compliant properties (by using getters and setters).
This is how you can access the Message
headers and payload properties.
A.1. SpEL Evaluation Context Customization
Starting with Spring Integration 3.0, you can add additional PropertyAccessor
instances to the SpEL evaluation contexts used by the framework.
The framework provides the (read-only) JsonPropertyAccessor
, which you can use to access fields from a JsonNode
or JSON in a String
.
You can also create your own PropertyAccessor
if you have specific needs.
In addition, you can add custom functions.
Custom functions are static
methods declared on a class.
Functions and property accessors are available in any SpEL expression used throughout the framework.
The following configuration shows how to directly configure the IntegrationEvaluationContextFactoryBean
with custom property accessors and functions:
<bean id="integrationEvaluationContext"
class="org.springframework.integration.config.IntegrationEvaluationContextFactoryBean">
<property name="propertyAccessors">
<util:map>
<entry key="things">
<bean class="things.MyCustomPropertyAccessor"/>
</entry>
</util:map>
</property>
<property name="functions">
<map>
<entry key="barcalc" value="#{T(things.MyFunctions).getMethod('calc', T(things.MyThing))}"/>
</map>
</property>
</bean>
For convenience, Spring Integration provides namespace support for both property accessors and functions, as described in the following sections. The framework automatically configures the factory bean on your behalf.
This factory bean definition overrides the default integrationEvaluationContext
bean definition.
It adds the custom accessor and one custom function to the list (which also includes the standard accessors mentioned earlier).
Note that custom functions are static methods.
In the preceding example, the custom function is a static method called calc
on a class called MyFunctions
and takes a single parameter of type MyThing
.
Suppose you have a Message
with a payload that has a type of MyThing
.
Further suppose that you need to perform some action to create an object called MyObject
from MyThing
and then invoke a custom function called calc
on that object.
The standard property accessors do not know how to get a MyObject
from a MyThing
, so you could write and configure a custom property accessor to do so.
As a result, your final expression might be "#barcalc(payload.myObject)"
.
The factory bean has another property (typeLocator
), which lets you customize the TypeLocator
used during SpEL evaluation.
You might need to do so running in some environments that use a non-standard ClassLoader
.
In the following example, SpEL expressions always use the bean factory’s class loader:
<bean id="integrationEvaluationContext"
class="org.springframework.integration.config.IntegrationEvaluationContextFactoryBean">
<property name="typeLocator">
<bean class="org.springframework.expression.spel.support.StandardTypeLocator">
<constructor-arg value="#{beanFactory.beanClassLoader}"/>
</bean>
</property>
</bean>
A.2. SpEL Functions
Spring Integration provides namespace support to let you create SpEL custom functions.
You can specify <spel-function/>
components to provide custom SpEL functions to the EvaluationContext
used throughout the framework.
Instead of configuring the factory bean shown earlier, you can add one or more of these components, and the framework automatically adds them to the default integrationEvaluationContext
factory bean.
For example, suppose you have a useful static method to evaluate XPath. The following example shows how you can create a custom function to use that method:
<int:spel-function id="xpath"
class="com.something.test.XPathUtils" method="evaluate(java.lang.String, java.lang.Object)"/>
<int:transformer input-channel="in" output-channel="out"
expression="#xpath('//things/@mythings', payload)" />
Given the preceding example:
-
The default
IntegrationEvaluationContextFactoryBean
bean with an ID ofintegrationEvaluationContext
is registered with the application context. -
The
<spel-function/>
is parsed and added to thefunctions
Map
ofintegrationEvaluationContext
as a map entry with itsid
as the key and the staticMethod
as the value. -
The
integrationEvaluationContext
factory bean creates a newStandardEvaluationContext
instance, and it is configured with the defaultPropertyAccessor
instances, aBeanResolver
, and the custom functions. -
That
EvaluationContext
instance is injected into theExpressionEvaluatingTransformer
bean.
To provide a SpEL Function by using Java configuration, you can declare a SpelFunctionFactoryBean
bean for each function.
The following example shows how to create a custom function:
@Bean
public SpelFunctionFactoryBean xpath() {
return new SpelFunctionFactoryBean(XPathUtils.class, "evaluate");
}
SpEL functions declared in a parent context are also made available in any child contexts.
Each context has its own instance of the integrationEvaluationContext factory bean, because each needs a different BeanResolver , but the function declarations are inherited and can be overridden by declaring a SpEL function with the same name.
|
A.2.1. Built-in SpEL Functions
Spring Integration provides the folloiwng standard functions, which are registered with the application context automatically on start up:
-
#jsonPath
: Evaluates a 'jsonPath' on a specified object. This function invokesJsonPathUtils.evaluate(…)
, which delegates to the Jayway JsonPath library. The following listing shows some usage examples:<transformer expression="#jsonPath(payload, '$.store.book[0].author')"/> <filter expression="#jsonPath(payload,'$..book[2].isbn') matches '\d-\d{3}-\d{5}-\d'"/> <splitter expression="#jsonPath(payload, '$.store.book')"/> <router expression="#jsonPath(payload, headers.jsonPath)"> <mapping channel="output1" value="reference"/> <mapping channel="output2" value="fiction"/> </router>
#jsonPath
also supports a third (optional) parameter: an array ofcom.jayway.jsonpath.Filter
, which can be provided by a reference to a bean or bean method (for example).Using this function requires the Jayway JsonPath library ( json-path.jar
) to be on the classpath. Otherwise the#jsonPath
SpEL function is not registered.For more information regarding JSON see 'JSON Transformers' in Transformer.
-
#xpath
: To evaluate an 'xpath' on some provided object. For more information regarding XML and XPath, see XML Support - Dealing with XML Payloads.
A.3. Property Accessors
Spring Integration provides namespace support to let you create SpEL custom PropertyAccessor
implementations.
You can use the <spel-property-accessors/>
component to provide a list of custom PropertyAccessor
instances to the EvaluationContext
used throughout the framework.
Instead of configuring the factory bean shown earlier, you can add one or more of these components, and the framework automatically adds the accessors to the default integrationEvaluationContext
factory bean.
The following example shows how to do so:
<int:spel-property-accessors>
<bean id="jsonPA" class="org.springframework.integration.json.JsonPropertyAccessor"/>
<ref bean="fooPropertyAccessor"/>
</int:spel-property-accessors>
In the preceding example, two custom PropertyAccessor
instances are injected into the EvaluationContext
(in the order in which they are declared).
To provide PropertyAccessor
instances by using Java Configuration, you should declare a SpelPropertyAccessorRegistrar
bean with a name of spelPropertyAccessorRegistrar
(dictated by the IntegrationContextUtils.SPEL_PROPERTY_ACCESSOR_REGISTRAR_BEAN_NAME
constant).
The following example shows how to configure two custom PropertyAccessor
instances with Java:
@Bean
public SpelPropertyAccessorRegistrar spelPropertyAccessorRegistrar() {
return new SpelPropertyAccessorRegistrar(new JsonPropertyAccessor())
.add(fooPropertyAccessor());
}
|
Custom
|
Appendix B: Message Publishing
The (Aspect-oriented Programming) AOP message publishing feature lets you construct and send a message as a by-product of a method invocation. For example, imagine you have a component and, every time the state of this component changes, you want to be notified by a message. The easiest way to send such notifications is to send a message to a dedicated channel, but how would you connect the method invocation that changes the state of the object to a message sending process, and how should the notification message be structured? The AOP message publishing feature handles these responsibilities with a configuration-driven approach.
B.1. Message Publishing Configuration
Spring Integration provides two approaches: XML configuration and annotation-driven (Java) configuration.
B.1.1. Annotation-driven Configuration with the @Publisher
Annotation
The annotation-driven approach lets you annotate any method with the @Publisher
annotation to specify a 'channel' attribute.
Starting with version 5.1, to switch this functionality on, you must use the @EnablePublisher
annotation on some @Configuration
class.
See Configuration and @EnableIntegration
for more information.
The message is constructed from the return value of the method invocation and sent to the channel specified by the 'channel' attribute.
To further manage message structure, you can also use a combination of both @Payload
and @Header
annotations.
Internally, this message publishing feature of Spring Integration uses both Spring AOP by defining PublisherAnnotationAdvisor
and the Spring Expression Language (SpEL), giving you considerable flexibility and control over the structure of the Message
it publishes.
The PublisherAnnotationAdvisor
defines and binds the following variables:
-
#return
: Binds to a return value, letting you reference it or its attributes (for example,#return.something
, where 'something' is an attribute of the object bound to#return
) -
#exception
: Binds to an exception if one is thrown by the method invocation -
#args
: Binds to method arguments so that you can extract individual arguments by name (for example,#args.fname
)
Consider the following example:
@Publisher
public String defaultPayload(String fname, String lname) {
return fname + " " + lname;
}
In the preceding example, the message is constructed with the following structure:
-
The message payload is the return type and value of the method. This is the default.
-
A newly constructed message is sent to a default publisher channel that is configured with an annotation post processor (covered later in this section).
The following example is the same as the preceding example, except that it does not use a default publishing channel:
@Publisher(channel="testChannel")
public String defaultPayload(String fname, @Header("last") String lname) {
return fname + " " + lname;
}
Instead of using a default publishing channel, we specify the publishing channel by setting the 'channel' attribute of the @Publisher
annotation.
We also add a @Header
annotation, which results in the message header named 'last' having the same value as the 'lname' method parameter.
That header is added to the newly constructed message.
The following example is almost identical to the preceding example:
@Publisher(channel="testChannel")
@Payload
public String defaultPayloadButExplicitAnnotation(String fname, @Header String lname) {
return fname + " " + lname;
}
The only difference is that we use a @Payload
annotation on the method to explicitly specify that the return value of the method should be used as the payload of the message.
The following example expands on the previous configuration by using the Spring Expression Language in the @Payload
annotation to further instruct the framework about how the message should be constructed:
@Publisher(channel="testChannel")
@Payload("#return + #args.lname")
public String setName(String fname, String lname, @Header("x") int num) {
return fname + " " + lname;
}
In the preceding example, the message is a concatenation of the return value of the method invocation and the 'lname' input argument. The Message header named 'x' has its value determined by the 'num' input argument. That header is added to the newly constructed message.
@Publisher(channel="testChannel")
public String argumentAsPayload(@Payload String fname, @Header String lname) {
return fname + " " + lname;
}
In the preceding example, you see another usage of the @Payload
annotation.
Here, we annotate a method argument that becomes the payload of the newly constructed message.
As with most other annotation-driven features in Spring, you need to register a post-processor (PublisherAnnotationBeanPostProcessor
).
The following example shows how to do so:
<bean class="org.springframework.integration.aop.PublisherAnnotationBeanPostProcessor"/>
For a more concise configuration, you can instead use namespace support, as the following example shows:
<int:annotation-config>
<int:enable-publisher default-publisher-channel="defaultChannel"/>
</int:annotation-config>
For Java configuration, you must use the @EnablePublisher
annotation, as the following example shows:
@Configuration
@EnableIntegration
@EnablePublisher("defaultChannel")
public class IntegrationConfiguration {
...
}
Starting with version 5.1.3, the <int:enable-publisher>
component, as well as the @EnablePublisher
annotation have the proxy-target-class
and order
attributes for tuning the ProxyFactory
configuration.
Similar to other Spring annotations (@Component
, @Scheduled
, and so on), you can also use @Publisher
as a meta-annotation.
This means that you can define your own annotations that are treated in the same way as the @Publisher
itself.
The following example shows how to do so:
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Publisher(channel="auditChannel")
public @interface Audit {
...
}
In the preceding example, we define the @Audit
annotation, which is itself annotated with @Publisher
.
Also note that you can define a channel
attribute on the meta-annotation to encapsulate where messages are sent inside of this annotation.
Now you can annotate any method with the @Audit
annotation, as the following example shows:
@Audit
public String test() {
return "Hello";
}
In the preceding example, every invocation of the test()
method results in a message with a payload created from its return value.
Each message is sent to the channel named auditChannel
.
One of the benefits of this technique is that you can avoid the duplication of the same channel name across multiple annotations.
You also can provide a level of indirection between your own, potentially domain-specific, annotations and those provided by the framework.
You can also annotate the class, which lets you apply the properties of this annotation on every public method of that class, as the following example shows:
@Audit
static class BankingOperationsImpl implements BankingOperations {
public String debit(String amount) {
. . .
}
public String credit(String amount) {
. . .
}
}
B.1.2. XML-based Approach with the <publishing-interceptor>
element
The XML-based approach lets you configure the same AOP-based message publishing functionality as a namespace-based configuration of a MessagePublishingInterceptor
.
It certainly has some benefits over the annotation-driven approach, since it lets you use AOP pointcut expressions, thus possibly intercepting multiple methods at once or intercepting and publishing methods to which you do not have the source code.
To configure message publishing with XML, you need only do the following two things:
-
Provide configuration for
MessagePublishingInterceptor
by using the<publishing-interceptor>
XML element. -
Provide AOP configuration to apply the
MessagePublishingInterceptor
to managed objects.
The following example shows how to configure a publishing-interceptor
element:
<aop:config>
<aop:advisor advice-ref="interceptor" pointcut="bean(testBean)" />
</aop:config>
<publishing-interceptor id="interceptor" default-channel="defaultChannel">
<method pattern="echo" payload="'Echoing: ' + #return" channel="echoChannel">
<header name="things" value="something"/>
</method>
<method pattern="repl*" payload="'Echoing: ' + #return" channel="echoChannel">
<header name="things" expression="'something'.toUpperCase()"/>
</method>
<method pattern="echoDef*" payload="#return"/>
</publishing-interceptor>
The <publishing-interceptor>
configuration looks rather similar to the annotation-based approach, and it also uses the power of the Spring Expression Language.
In the preceding example, the execution of the echo
method of a testBean
renders a Message
with the following structure:
-
The
Message
payload is of typeString
with the following content:Echoing: [value]
, wherevalue
is the value returned by an executed method. -
The
Message
has a header with a name ofthings
and a value ofsomething
. -
The
Message
is sent toechoChannel
.
The second method is very similar to the first.
Here, every method that begins with 'repl' renders a Message
with the following structure:
-
The
Message
payload is the same as in the preceding sample. -
The
Message
has a header namedthings
whose value is the result of the SpEL expression'something'.toUpperCase()
. -
The
Message
is sent toechoChannel
.
The second method, mapping the execution of any method that begins with echoDef
, produces a Message
with the following structure:
-
The
Message
payload is the value returned by an executed method. -
Since the
channel
attribute is not provided, theMessage
is sent to thedefaultChannel
defined by thepublisher
.
For simple mapping rules you can rely on the publisher
defaults, as the following example shows:
<publishing-interceptor id="anotherInterceptor"/>
The preceding example maps the return value of every method that matches the pointcut expression to a payload and is sent to a default-channel
.
If you do not specify the defaultChannel
(as the preceding example does not do), the messages are sent to the global nullChannel
(the equivalent of /dev/null
).
Asynchronous Publishing
Publishing occurs in the same thread as your component’s execution. So, by default, it is synchronous. This means that the entire message flow has to wait until the publisher’s flow completes. However, developers often want the complete opposite: to use this message-publishing feature to initiate asynchronous flows. For example, you might host a service (HTTP, WS, and so on) which receives a remote request. You may want to send this request internally into a process that might take a while. However you may also want to reply to the user right away. So, instead of sending inbound requests for processing to the output channel (the conventional way), you can use 'output-channel' or a 'replyChannel' header to send a simple acknowledgment-like reply back to the caller while using the message-publisher feature to initiate a complex flow.
The service in the following example receives a complex payload (which needs to be sent further for processing), but it also needs to reply to the caller with a simple acknowledgment:
public String echo(Object complexPayload) {
return "ACK";
}
So, instead of hooking up the complex flow to the output channel, we use the message-publishing feature instead.
We configure it to create a new message, by using the input argument of the service method (shown in the preceding example), and send that to the 'localProcessChannel'.
To make sure this flow is asynchronous, all we need to do is send it to any type of asynchronous channel (ExecutorChannel
in the next example).
The following example shows how to an asynchronous publishing-interceptor
:
<int:service-activator input-channel="inputChannel" output-channel="outputChannel" ref="sampleservice"/>
<bean id="sampleservice" class="test.SampleService"/>
<aop:config>
<aop:advisor advice-ref="interceptor" pointcut="bean(sampleservice)" />
</aop:config>
<int:publishing-interceptor id="interceptor" >
<int:method pattern="echo" payload="#args[0]" channel="localProcessChannel">
<int:header name="sample_header" expression="'some sample value'"/>
</int:method>
</int:publishing-interceptor>
<int:channel id="localProcessChannel">
<int:dispatcher task-executor="executor"/>
</int:channel>
<task:executor id="executor" pool-size="5"/>
Another way of handling this type of scenario is with a wire-tap. See Wire Tap.
B.1.3. Producing and Publishing Messages Based on a Scheduled Trigger
In the preceding sections, we looked at the message-publishing feature, which constructs and publishes messages as by-products of method invocations.
However, in those cases, you are still responsible for invoking the method.
Spring Integration 2.0 added support for scheduled message producers and publishers with the new expression
attribute on the 'inbound-channel-adapter' element.
You can schedule based on several triggers, any one of which can be configured on the 'poller' element.
Currently, we support cron
, fixed-rate
, fixed-delay
and any custom trigger implemented by you and referenced by the 'trigger' attribute value.
As mentioned earlier, support for scheduled producers and publishers is provided via the <inbound-channel-adapter>
XML element.
Consider the following example:
<int:inbound-channel-adapter id="fixedDelayProducer"
expression="'fixedDelayTest'"
channel="fixedDelayChannel">
<int:poller fixed-delay="1000"/>
</int:inbound-channel-adapter>
The preceding example creates an inbound channel adapter that constructs a Message
, with its payload being the result of the expression defined in the expression
attribute.
Such messages are created and sent every time the delay specified by the fixed-delay
attribute occurs.
The following example is similar to the preceding example, except that it uses the fixed-rate
attribute:
<int:inbound-channel-adapter id="fixedRateProducer"
expression="'fixedRateTest'"
channel="fixedRateChannel">
<int:poller fixed-rate="1000"/>
</int:inbound-channel-adapter>
The fixed-rate
attribute lets you send messages at a fixed rate (measuring from the start time of each task).
The following example shows how you can apply a Cron trigger with a value specified in the cron
attribute:
<int:inbound-channel-adapter id="cronProducer"
expression="'cronTest'"
channel="cronChannel">
<int:poller cron="7 6 5 4 3 ?"/>
</int:inbound-channel-adapter>
The following example shows how to insert additional headers into the message:
<int:inbound-channel-adapter id="headerExpressionsProducer"
expression="'headerExpressionsTest'"
channel="headerExpressionsChannel"
auto-startup="false">
<int:poller fixed-delay="5000"/>
<int:header name="foo" expression="6 * 7"/>
<int:header name="bar" value="x"/>
</int:inbound-channel-adapter>
The additional message headers can take scalar values or the results of evaluating Spring expressions.
If you need to implement your own custom trigger, you can use the trigger
attribute to provide a reference to any spring configured bean that implements the org.springframework.scheduling.Trigger
interface.
The following example shows how to do so:
<int:inbound-channel-adapter id="triggerRefProducer"
expression="'triggerRefTest'" channel="triggerRefChannel">
<int:poller trigger="customTrigger"/>
</int:inbound-channel-adapter>
<beans:bean id="customTrigger" class="o.s.scheduling.support.PeriodicTrigger">
<beans:constructor-arg value="9999"/>
</beans:bean>
Appendix C: Transaction Support
This chapter covers Spring Integration’s support for transactions. It covers the following topics:
C.1. Understanding Transactions in Message flows
Spring Integration exposes several hooks to address the transactional needs of your message flows. To better understand these hooks and how you can benefit from them, we must first revisit the six mechanisms that you can use to initiate message flows and see how you can address the transactional needs of these flows within each of these mechanisms.
The following six mechanisms initiate a message flow (details for each are provided throughout this manual):
-
Gateway proxy: A basic messaging gateway.
-
Message channel: Direct interactions with
MessageChannel
methods (for example,channel.send(message)
). -
Message publisher: The way to initiate a message flow as the by-product of method invocations on Spring beans.
-
Inbound channel adapters and gateways: The way to initiate a message flow based on connecting third-party system with the Spring Integration messaging system (for example,
[JmsMessage] → Jms Inbound Adapter[SI Message] → SI Channel
). -
Scheduler: The way to initiate a message flow based on scheduling events distributed by a pre-configured scheduler.
-
Poller: Similar to the scheduler, this is the way to initiate message flow based on scheduling or interval-based events distributed by a pre-configured poller.
We can split these six mechanisms into two general categories:
-
Message flows initiated by a user process: Example scenarios in this category would be invoking a gateway method or explicitly sending a
Message
to aMessageChannel
. In other words, these message flows depend on a third party process (such as some code that you wrote) to be initiated. -
Message flows initiated by a daemon process: Example scenarios in this category include a Poller polling a message queue to initiate a new message flow with the polled message or a scheduler scheduling the process by creating a new message and initiating a message flow at a predefined time.
Clearly the gateway proxy, MessageChannel.send(…)
and MessagePublisher
all belong to the first category, and inbound adapters and gateways, scheduler, and poller belong to the second category.
So, how can you address transactional needs in various scenarios within each category, and is there a need for Spring Integration to provide something explicit with regard to transactions for a particular scenario? Or can you use Spring’s transaction support instead?
Spring itself provides first class support for transaction management. So our goal here is not to provide something new but rather use Spring to benefit from its existing support for transactions. In other words, as a framework, we must expose hooks to Spring’s transaction management functionality. However, since Spring Integration configuration is based on Spring configuration, we need not always expose these hooks, because Spring already exposes them . After all, every Spring Integration component is a Spring Bean.
With this goal in mind, we can again consider the two scenarios: messgae flows initiated by a user process and message flows initiated by a daemon.
Message flows that are initiated by a user process and configured in a Spring application context are subject to the usual transactional configuration of such processes.
Therefore they need not be explicitly configured by Spring Integration to support transactions.
The transaction could and should be initiated through Spring’s standard transaction support.
The Spring Integration message flow naturally honors the transactional semantics of the components, because it is itself configured by Spring.
For example, a gateway or service activator method could be annotated with @Transactional
, or a TransactionInterceptor
could be defined in an XML configuration with a pointcut expression that pointa to specific methods that should be transactional.
The bottom line is that you have full control over transaction configuration and boundaries in these scenarios.
However, things are a bit different when it comes to message flows initiated by a daemon process. Although configured by the developer, these flows do not directly involve a human or some other process to be initiated. These are trigger-based flows that are initiated by a trigger process (a daemon process) based on the configuration of the process. For example, we could have a scheduler initiate a message flow every Friday night. We can also configure a trigger that initiates a message flow every second and so on. As a result, we need a way to let these trigger-based processes know of our intention to make the resulting message flows be transactional, so that a Transaction context can be created whenever a new message flow is initiated. In other words, we need to expose some transaction configuration, but only enough to delegate to the transaction support already provided by Spring (as we do in other scenarios).
C.1.1. Poller Transaction Support
Spring Integration provides transactional support for pollers.
Pollers are a special type of component because, within a poller task, we can call receive()
against a resource that is itself transactional, thus including the receive()
call in the the boundaries of the transaction, which lets it be rolled back in case of a task failure.
If we were to add the same support for channels, the added transactions would affect all downstream components starting with the send()
call.
That provides a rather wide scope for transaction demarcation without any strong reason, especially when Spring already provides several ways to address the transactional needs of any component downstream.
However the receive()
method being included in a transaction boundary is the “strong reason” for pollers.
Any time you configure a Poller, you can provide transactional configuration by using the transactional
child element and its attributes,as the following example shows:
<int:poller max-messages-per-poll="1" fixed-rate="1000">
<transactional transaction-manager="txManager"
isolation="DEFAULT"
propagation="REQUIRED"
read-only="true"
timeout="1000"/>
</poller>
The preceding configuration looks similar to a native Spring transaction configuration.
You must still provide a reference to a transaction manager and either specify transaction attributes or rely on defaults (for example, if the 'transaction-manager' attribute is not specified, it defaults to the bean named 'transactionManager').
Internally, the process is wrapped in Spring’s native transaction, where TransactionInterceptor
is responsible for handling transactions.
For more information on how to configure a transaction manager, the types of transaction managers (such as JTA, Datasource, and others), and other details related to transaction configuration, see the Spring Framework Reference Guide.
With the preceding configuration, all message flows initiated by this poller are transactional. For more information and details on a poller’s transactional configuration, see Polling and Transactions.
Along with transactions, you might need to address several more cross-cutting concerns when you run a poller.
To help with that, the poller element accepts an <advice-chain>
child element, which lets you define a custom chain of advice instances to be applied on the Poller.
(See Pollable Message Source for more details.)
In Spring Integration 2.0, the Poller went through a refactoring effort and now uses a proxy mechanism to address transactional concerns as well as other cross-cutting concerns.
One of the significant changes evolving from this effort is that we made the <transactional>
and <advice-chain>
elements be mutually exclusive.
The rationale behind this is that, if you need more than one advice and one of them is Transaction advice, you can include it in the <advice-chain>
with the same convenience as before but with much more control, since you now have an option to position the advice in the desired order.
The following example shows how to do so:
<int:poller max-messages-per-poll="1" fixed-rate="10000">
<advice-chain>
<ref bean="txAdvice"/>
<ref bean="someAotherAdviceBean" />
<beans:bean class="foo.bar.SampleAdvice"/>
</advice-chain>
</poller>
<tx:advice id="txAdvice" transaction-manager="txManager">
<tx:attributes>
<tx:method name="get*" read-only="true"/>
<tx:method name="*"/>
</tx:attributes>
</tx:advice>
The preceding example shows a basic XML-based configuration of Spring Transaction advice (txAdvice
) and included it within the <advice-chain>
defined by the Poller.
If you need to address only the transactional concerns of the poller, you can still use the <transactional>
element as a convenience.
C.2. Transaction Boundaries
Another important factor is the boundaries of Transactions within a Message flow. When a transaction is started, the transaction context is bound to the current thread. So regardless of how many endpoints and channels you have in your Message flow your transaction context will be preserved as long as you are ensuring that the flow continues on the same thread. As soon as you break it by introducing a Pollable Channel or Executor Channel or initiate a new thread manually in some service, the Transactional boundary will be broken as well. Essentially the Transaction will END right there, and if a successful handoff has transpired between the threads, the flow would be considered a success and a COMMIT signal would be sent even though the flow will continue and might still result in an Exception somewhere downstream. If such a flow were synchronous, that Exception could be thrown back to the initiator of the Message flow who is also the initiator of the transactional context and the transaction would result in a ROLLBACK. The middle ground is to use transactional channels at any point where a thread boundary is being broken. For example, you can use a Queue-backed Channel that delegates to a transactional MessageStore strategy, or you could use a JMS-backed channel.
C.3. Transaction Synchronization
In some environments, it help to synchronize operations with a transaction that encompasses the entire flow.
For example, consider a <file:inbound-channel-adapter/>
at the start of a flow that performs a number of database updates.
If the transaction commits, we might want to move the file to a success
directory, while we might want to move it to a failure
directory if the transaction rolls back.
Spring Integration 2.2 introduced the capability of synchronizing these operations with a transaction.
In addition, you can configure a PseudoTransactionManager
if you do not have a 'real' transaction but still want to perform different actions on success or failure.
For more information, see Pseudo Transactions.
The following listing shows the key strategy interfaces for this feature:
public interface TransactionSynchronizationFactory {
TransactionSynchronization create(Object key);
}
public interface TransactionSynchronizationProcessor {
void processBeforeCommit(IntegrationResourceHolder holder);
void processAfterCommit(IntegrationResourceHolder holder);
void processAfterRollback(IntegrationResourceHolder holder);
}
The factory is responsible for creating a TransactionSynchronization
object.
You can implement your own or use the one provided by the framework: DefaultTransactionSynchronizationFactory
.
This implementation returns a TransactionSynchronization
that delegates to a default implementation of TransactionSynchronizationProcessor
: ExpressionEvaluatingTransactionSynchronizationProcessor
.
This processor supports three SpEL expressions: beforeCommitExpression
, afterCommitExpression
, and afterRollbackExpression
.
These actions should be self-explanatory to those familiar with transactions.
In each case, the #root
variable is the original Message
.
In some cases, other SpEL variables are made available, depending on the MessageSource
being polled by the poller.
For example, the MongoDbMessageSource
provides the #mongoTemplate
variable, which references the message source’s MongoTemplate
.
Similarly, the RedisStoreMessageSource
provides the #store
variable, which references the RedisStore
created by the poll.
To enable the feature for a particular poller, you can provide a reference to the TransactionSynchronizationFactory
on the poller’s <transactional/>
element by using the synchronization-factory
attribute.
Starting with version 5.0, Spring Integration provides PassThroughTransactionSynchronizationFactory
, which is applied by default to polling endpoints when no TransactionSynchronizationFactory
is configured but an advice of type TransactionInterceptor
exists in the advice chain.
When using any out-of-the-box TransactionSynchronizationFactory
implementation, polling endpoints bind a polled message to the current transactional context and provide it as a failedMessage
in a MessagingException
if an exception is thrown after the transaction advice.
When using a custom transaction advice that does not implement TransactionInterceptor
, you can explicitly configure a PassThroughTransactionSynchronizationFactory
to achieve this behavior.
In either case, the MessagingException
becomes the payload of the ErrorMessage
that is sent to the errorChannel
, and the cause is the raw exception thrown by the advice.
Previously, the ErrorMessage
had a payload that was the raw exception thrown by the advice and did not provide a reference to the failedMessage
information, making it difficult to determine the reasons for the transaction commit problem.
To simplify configuration of these components, Spring Integration provides namespace support for the default factory. The following example shows how to use the namespace to configure a file inbound channel adapter:
<int-file:inbound-channel-adapter id="inputDirPoller"
channel="someChannel"
directory="/foo/bar"
filter="filter"
comparator="testComparator">
<int:poller fixed-rate="5000">
<int:transactional transaction-manager="transactionManager" synchronization-factory="syncFactory" />
</int:poller>
</int-file:inbound-channel-adapter>
<int:transaction-synchronization-factory id="syncFactory">
<int:after-commit expression="payload.renameTo(new java.io.File('/success/' + payload.name))"
channel="committedChannel" />
<int:after-rollback expression="payload.renameTo(new java.io.File('/failed/' + payload.name))"
channel="rolledBackChannel" />
</int:transaction-synchronization-factory>
The result of the SpEL evaluation is sent as the payload to either committedChannel
or rolledBackChannel
(in this case, this would be Boolean.TRUE
or Boolean.FALSE
— the result of the java.io.File.renameTo()
method call).
If you wish to send the entire payload for further Spring Integration processing, use the 'payload' expression.
|
It is important to understand that this synchronizes the actions with a transaction. It does not make a resource that is not inherently transactional actually be transactional. Instead, the transaction (be it JDBC or otherwise) is started before the poll and either committed or rolled back when the flow completes, followed by the synchronized action. If you provide a custom |
In addition to the after-commit
and after-rollback
expressions, before-commit
is also supported.
In that case, if the evaluation (or downstream processing) throws an exception, the transaction is rolled back instead of being committed.
C.4. Pseudo Transactions
After reading the Transaction Synchronization section, you might think it would be useful to take these 'success' or 'failure' actions when a flow completes, even if there is no “real” transactional resources (such as JDBC) downstream of the poller. For example, consider a “<file:inbound-channel-adapter/>” followed by an “<ftp:outbout-channel-adapter/>”. Neither of these components is transactional, but we might want to move the input file to different directories, based on the success or failure of the FTP transfer.
To provide this functionality, the framework provides a PseudoTransactionManager
, enabling the above configuration even when there is no real transactional resource involved.
If the flow completes normally, the beforeCommit
and afterCommit
synchronizations are called.
On failure, the afterRollback
synchronization is called.
Because it is not a real transaction, no actual commit or rollback occurs.
The pseudo transaction is a vehicle used to enable the synchronization features.
To use a PseudoTransactionManager
, you can define it as a <bean/>, in the same way you would configure a real transaction manager.
The following example shows how to do so:
<bean id="transactionManager" class="o.s.i.transaction.PseudoTransactionManager" />
Appendix D: Security in Spring Integration
Security is one of the important functions in any modern enterprise (or cloud) application.
Moreover, it is critical for distributed systems, such as those built on Enterprise Integration Patterns.
Messaging independence and loose coupling let target systems communicate with each other with any type of data in the message’s payload
.
We can either trust all those messages or secure our service against “infecting” messages.
Spring Integration, together with Spring Security, provides a simple and comprehensive way to secure message channels, as well as other part of the integration solution.
You need to include this dependency into your project:
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-security</artifactId>
<version>5.1.5.RELEASE</version>
</dependency>
compile "org.springframework.integration:spring-integration-security:5.1.5.RELEASE"
D.1. Securing channels
Spring Integration provides the ChannelSecurityInterceptor
interceptor, which extends AbstractSecurityInterceptor
and intercepts send and receive calls on the channel.
Access decisions are then made with reference to a ChannelSecurityMetadataSource
, which provides the metadata that describes the send and receive access policies for certain channels.
The interceptor requires that a valid SecurityContext
has been established by authenticating with Spring Security.
See the Spring Security Reference Guide for details.
Spring Integration provides Namespace support to allow easy configuration of security constraints.
This support consists of the secured channels tag, which allows definition of one or more channel name patterns in conjunction with a definition of the security configuration for send and receive.
The pattern is a java.util.regexp.Pattern
.
The following example shows how to configure a bean that includes security and how to set up policies with patterns:
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns:int="http://www.springframework.org/schema/integration"
xmlns:int-security="http://www.springframework.org/schema/integration/security"
xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:security="http://www.springframework.org/schema/security"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/security
https://www.springframework.org/schema/security/spring-security.xsd
http://www.springframework.org/schema/integration
https://www.springframework.org/schema/integration/spring-integration.xsd
http://www.springframework.org/schema/integration/security
https://www.springframework.org/schema/integration/security/spring-integration-security.xsd">
<int-security:secured-channels>
<int-security:access-policy pattern="admin.*" send-access="ROLE_ADMIN"/>
<int-security:access-policy pattern="user.*" receive-access="ROLE_USER"/>
</int-security:secured-channels>
By default, the secured-channels
namespace element expects a bean named authenticationManager
(which implements AuthenticationManager
) and a bean named accessDecisionManager
(which implements AccessDecisionManager
).
Where this is not the case, references to the appropriate beans can be configured as attributes of the secured-channels
element, as the following example shows:
<int-security:secured-channels access-decision-manager="customAccessDecisionManager"
authentication-manager="customAuthenticationManager">
<int-security:access-policy pattern="admin.*" send-access="ROLE_ADMIN"/>
<int-security:access-policy pattern="user.*" receive-access="ROLE_USER"/>
</int-security:secured-channels>
Starting with version 4.2, the @SecuredChannel
annotation is available for Java configuration in @Configuration
classes.
The following example shows the Java equivalent of the preceding XML examples:
@Configuration
@EnableIntegration
public class ContextConfiguration {
@Bean
@SecuredChannel(interceptor = "channelSecurityInterceptor", sendAccess = "ROLE_ADMIN")
public SubscribableChannel adminChannel() {
return new DirectChannel();
}
@Bean
@SecuredChannel(interceptor = "channelSecurityInterceptor", receiveAccess = "ROLE_USER")
public SubscribableChannel userChannel() {
return new DirectChannel();
}
@Bean
public ChannelSecurityInterceptor channelSecurityInterceptor(
AuthenticationManager authenticationManager,
AccessDecisionManager accessDecisionManager) {
ChannelSecurityInterceptor channelSecurityInterceptor = new ChannelSecurityInterceptor();
channelSecurityInterceptor.setAuthenticationManager(authenticationManager);
channelSecurityInterceptor.setAccessDecisionManager(accessDecisionManager);
return channelSecurityInterceptor;
}
}
D.2. Security Context Propagation
To be sure that our interaction with the application is secure, according to its security system rules, we should supply some security context with an authentication (principal) object.
The Spring Security project provides a flexible, canonical mechanism to authenticate our application clients over HTTP, WebSocket, or SOAP protocols (as can be done for any other integration protocol with a simple Spring Security extension).
It also provides a SecurityContext
for further authorization checks on the application objects, such as message channels.
By default, the SecurityContext
is tied to the execution state of the current Thread
by using the (ThreadLocalSecurityContextHolderStrategy
).
It is accessed by an AOP (Aspect-oriented Programming) interceptor on secured methods to check (for example) whether that principal
of the invocation has sufficient permissions to call that method.
This works well with the current thread.
Often, though, processing logic can be performed on another thread, on several threads, or even on external systems.
Standard thread-bound behavior is easy to configure if our application is built on the Spring Integration components
and its message channels.
In this case, the secured objects can be any service activator or transformer, secured with a
MethodSecurityInterceptor
in their <request-handler-advice-chain>
(see Adding Behavior to Endpoints) or even MessageChannel
(see Securing channels, earlier).
When using DirectChannel
communication, the SecurityContext
is automatically available, because the downstream flow runs on the current thread.
However, in the cases of the QueueChannel
, ExecutorChannel
, and PublishSubscribeChannel
with an Executor
, messages are transferred from one thread to another (or several) by the nature of those channels.
In order to support such scenarios, we have two choices:
-
Transfer an
Authentication
object within the message headers and extract and authenticate it on the other side before secured object access. -
Propagate the
SecurityContext
to the thread that receives the transferred message.
Version 4.2 introduced SecurityContext
propagation.
It is implemented as a SecurityContextPropagationChannelInterceptor
, which you can add to any MessageChannel
or configure as a @GlobalChannelInterceptor
.
The logic of this interceptor is based on the SecurityContext
extraction from the current thread (from the preSend()
method) and its populating to another thread from the postReceive()
(beforeHandle()
) method.
Actually, this interceptor is an extension of the more generic ThreadStatePropagationChannelInterceptor
, which wraps the message to send with the state to propagate in an internal Message<?>
extension (MessageWithThreadState<S>
) on one side and extracts the original message and the state to propagate on the other side.
You can extend the ThreadStatePropagationChannelInterceptor
for any context propagation use case, and SecurityContextPropagationChannelInterceptor
is a good example of doing so.
The logic of the ThreadStatePropagationChannelInterceptor is based on message modification (it returns an internal MessageWithThreadState object to send).
Consequently, you should be careful when combining this interceptor with any other that can also modify messages (for example, through the MessageBuilder.withPayload(…)…build() ).
The state to propagate may be lost.
In most cases, to overcome the issue, you can order the interceptors for the channel and ensure the ThreadStatePropagationChannelInterceptor is the last one in the stack.
|
Propagation and population of SecurityContext
is just one half of the work.
Since the message is not an owner of the threads in the message flow and we should be sure that we are secure against any incoming messages, we have to clean up the SecurityContext
from ThreadLocal
.
The SecurityContextPropagationChannelInterceptor
provides the afterMessageHandled()
interceptor method implementation.
It cleans up operation by freeing the thread at the end of invocation from that propagated principal.
This means that, when the thread that processes the handed-off message finishes processing the message (successful or otherwise), the context is cleared so that it cannot inadvertently be used when processing another message.
|
When working with an asynchronous gateway, you should use an appropriate
|
Appendix E: Configuration
Spring Integration offers a number of configuration options. Which option you choose depends upon your particular needs and at what level you prefer to work. As with the Spring framework in general, you can mix and match the various techniques to suit the problem at hand. For example, you can choose the XSD-based namespace for the majority of configuration and combine it with a handful of objects that you configure with annotations. As much as possible, the two provide consistent naming. The XML elements defined by the XSD schema match the names of the annotations, and the attributes of those XML elements match the names of annotation properties. You can also use the API directly, but we expect most developers to choose one of the higher-level options or a combination of the namespace-based and annotation-driven configuration.
E.1. Namespace Support
You can configure Spring Integration components with XML elements that map directly to the terminology and concepts of enterprise integration. In many cases, the element names match those of the Enterprise Integration Patterns book.
To enable Spring Integration’s core namespace support within your Spring configuration files, add the following namespace reference and schema mapping in your top-level 'beans' element:
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:int="http://www.springframework.org/schema/integration" xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/integration https://www.springframework.org/schema/integration/spring-integration.xsd">
(We have emphasized the lines that are particular to Spring Integration.)
You can choose any name after "xmlns:".
We use int
(short for Integration) for clarity, but you might prefer another abbreviation.
On the other hand, if you use an XML editor or IDE support, the availability of auto-completion may convince you to keep the longer name for clarity.
Alternatively, you can create configuration files that use the Spring Integration schema as the primary namespace, as the following example shows:
<beans:beans xmlns="http://www.springframework.org/schema/integration" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:beans="http://www.springframework.org/schema/beans" xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/integration https://www.springframework.org/schema/integration/spring-integration.xsd">
(We have emphasized the lines that are particular to Spring Integration.)
When using this alternative, no prefix is necessary for the Spring Integration elements.
On the other hand, if you define a generic Spring bean within the same configuration file, the bean element requires a prefix (<beans:bean …/>
).
Since it is generally a good idea to modularize the configuration files themselves (based on responsibility or architectural layer), you may find it appropriate to use the latter approach in the integration-focused configuration files, since generic beans are seldom necessary within those files.
For the purposes of this documentation, we assume the integration namespace is the primary.
Spring Integration provides many other namespaces.