diff --git a/src/reference/antora/modules/ROOT/pages/xml/transformation.adoc b/src/reference/antora/modules/ROOT/pages/xml/transformation.adoc index 42174341b8..fe4d88eade 100644 --- a/src/reference/antora/modules/ROOT/pages/xml/transformation.adoc +++ b/src/reference/antora/modules/ROOT/pages/xml/transformation.adoc @@ -15,7 +15,7 @@ This section will explain the workings of the following transformers and how to All the XML transformers extend either https://docs.spring.io/spring-integration/api/org/springframework/integration/transformer/AbstractTransformer.html[`AbstractTransformer`] or https://docs.spring.io/spring-integration/api/org/springframework/integration/transformer/AbstractPayloadTransformer.html[`AbstractPayloadTransformer`] and therefore implement https://docs.spring.io/spring-integration/api/org/springframework/integration/transformer/Transformer.html[`Transformer`]. When configuring XML transformers as beans in Spring Integration, you would normally configure the `Transformer` in conjunction with a https://docs.spring.io/spring-integration/api/org/springframework/integration/transformer/MessageTransformingHandler.html[`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. +Finally, we discuss configuring transformers using Java Configuration as well as XML namespace support, which allows for configuring the transformers as elements in XML. [[xml-unmarshalling-transformer]] === UnmarshallingTransformer @@ -35,7 +35,28 @@ See xref:ws.adoc#mtom-support[MTOM Support] for more information. The following example shows how to define an unmarshalling transformer: -[source,xml] +[tabs] +====== +Java:: ++ +[source,java,role="primary"] +---- + @Bean + public Jaxb2Marshaller jaxb2Marshaller() { + Jaxb2Marshaller marshaller = new Jaxb2Marshaller(); + marshaller.setContextPath("org.example"); + return marshaller; + } + + @Bean + public UnmarshallingTransformer unmarshallingTransformer(Jaxb2Marshaller jaxb2Marshaller) { + return new UnmarshallingTransformer(jaxb2Marshaller); + } +---- + +XML:: ++ +[source,xml,role="secondary"] ---- @@ -45,6 +66,7 @@ The following example shows how to define an unmarshalling transformer: ---- +====== [[xml-marshalling-transformer]] === Using `MarshallingTransformer` @@ -57,7 +79,21 @@ 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: -[source,xml] +[tabs] +====== +Java:: ++ +[source,java,role="primary"] +---- + @Bean + public MarshallingTransformer marshallingTransformer(Jaxb2Marshaller jaxb2Marshaller) { + return new MarshallingTransformer(jaxb2Marshaller, new ResultToDocumentTransformer()); + } +---- + +XML:: ++ +[source,xml,role="secondary"] ---- @@ -70,7 +106,7 @@ The following example configures a marshalling transformer that transforms to a ---- - +====== 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. @@ -95,7 +131,22 @@ You can customize this by providing a https://docs.spring.io/spring-integration/ The following example configures a bean that works as an XSLT payload transformer: -[source,xml] +[tabs] +====== +Java:: ++ +[source,java,role="primary"] +---- + @Bean + public XsltPayloadTransformer xsltPayloadTransformer() { + var xslResource = new ClassPathResource("org/example/xsl/transform.xsl"); + return new XsltPayloadTransformer(xslResource, new ResultToDocumentTransformer()); + } +---- + +XML:: ++ +[source,xml,role="secondary"] ---- @@ -104,9 +155,9 @@ The following example configures a bean that works as an XSLT payload transforme ---- +====== -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. +Starting with Spring Integration 3.0, you can now specify the transformer factory class name by setting `transformerFactoryClassName` in the constructor when using Java configuration or `transformer-factory-class` attribute when using XML configuration. [[xml-using-result-transformers]] === Using `ResultTransformer` Implementations @@ -127,23 +178,49 @@ By default, if the input payload is an instance of `String` or https://docs.orac However, if the input payload is a https://docs.oracle.com/javase/6/docs/api/javax/xml/transform/Source.html[`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 xref:xml/transformation.adoc#xml-using-result-transformers-namespace[Namespace Configuration and Result Transformers]. +For more information and examples, see xref:xml/transformation.adoc#configuration-and-result-transformers[Configuration and Result Transformers]. [[xml-transformer-namespace]] == Namespace Support for XML Transformers -Namespace support for all XML transformers is provided in the Spring Integration XML namespace, a template for which was xref:xml/xpath-namespace-support.adoc[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. +Configure XML transformers using Java DSL or XML namespace support. With Java configuration, define an IntegrationFlow bean and use Xml.* factory methods (such as Xml.xpathTransformer()) to create the transformer within the flow. +With XML configuration, use the Spring Integration XML namespace as xref:xml/xpath-namespace-support.adoc[shown earlier]. Both approaches create an instance of either `EventDrivenConsumer` or `PollingConsumer`, according to the type of the provided input channel. +Java DSL offers a fluent, type-safe API for building integration flows, while XML namespace support reduces configuration by combining endpoint and transformer creation into a single element. [[using-an-unmarshallingtransformer]] === Using an `UnmarshallingTransformer` -The namespace support for the `UnmarshallingTransformer` is shown below. -Since the namespace creates an endpoint instance rather than a transformer, you can nest a poller within the element to control the polling of the input channel. +Configure the `UnmarshallingTransformer` using Java DSL or XML namespace support. With Java configuration, create an IntegrationFlow bean and use Xml.unmarshaller() with your Unmarshaller implementation. With XML configuration, use the namespace support as shown below. Since both approaches create an endpoint instance rather than just a transformer, configure polling behavior directly on the endpoint. In Java DSL, specify the poller in the source endpoint configuration. In XML, nest a poller element within the transformer element to control the polling of the input channel. The following example shows how to do so: -[source,xml] +[tabs] +====== +Java:: ++ +[source,java,role="primary"] +---- +@Bean +public IntegrationFlow unmarshallingFlow(Unmarshaller unmarshaller) { + return IntegrationFlow.from("input") + .transform(new UnmarshallingTransformer(unmarshaller)) + .channel("output") + .get(); +} + +@Bean +public IntegrationFlow pollingUnmarshallingFlow(QueueChannel input, Unmarshaller unmarshaller) { + MessageSource messageSource = + () -> (org.springframework.messaging.Message) input.receive(1000); + return IntegrationFlow.from(messageSource, endpoint -> endpoint.poller(Pollers.fixedRate(2000))) + .transform(new UnmarshallingTransformer(unmarshaller)) + .channel("output") + .get(); +} +---- + +XML:: ++ +[source,xml,role="secondary"] ---- ---- +====== [[using-a-marshallingtransformer]] === 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. +Configuring the marshalling transformer requires an `input-channel`, an `output-channel`, and a reference to a `marshaller`. +With the XML namespace support 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: -[source,xml] +[tabs] +====== +Java:: ++ +[source,java,role="primary"] +---- +@Bean +public IntegrationFlow marshallingToStringFlow(Marshaller marshaller) { + return IntegrationFlow.from("marshallingTransformerStringResultFactory") + .transform(new MarshallingTransformer(marshaller)) + .channel("output") + .get(); +} + + @Bean + public IntegrationFlow marshallingWithResultType( + Marshaller marshaller) { + + MarshallingTransformer transformer = new MarshallingTransformer(marshaller); + transformer.setResultType(MarshallingTransformer.STRING_RESULT); + + return IntegrationFlow.from("marshallingTransformerWithResultTransformer") + .transform(transformer) + .channel("output") + .get(); + } +---- + +XML:: ++ +[source,xml,role="secondary"] ---- ---- - +====== 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. @@ -189,15 +297,49 @@ NOTE: Internally, the `StringResult` and `DomResult` result types are represente [[using-an-xsltpayloadtransformer]] === Using an `XsltPayloadTransformer` -Namespace support for the `XsltPayloadTransformer` lets you either pass in a `Resource` (in order to create the https://docs.oracle.com/javase/6/docs/api/javax/xml/transform/Templates.html[`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 a result before sending, you can use a `result-transformer` attribute to reference an implementation of `ResultTransformer`. +Configure the XsltPayloadTransformer using Java DSL or XML namespace support. +In Java configuration, use `Xml.xsltTransformer()` and pass either a `Resource` (to create the https://docs.oracle.com/javase/6/docs/api/javax/xml/transform/Templates.html[`Templates`] instance) or a pre-created Templates instance. +In XML configuration, specify either approach using the appropriate attribute. +As with the marshalling transformer, control the type of result output by specifying either the `result-factory` or the `result-type`. +In Java configuration set the `resultFactory` or `resultType` on the transformer. +In XML, set the corresponding `result-factory` or `result-type` attribute. +When you need to convert a result before sending, provide a ResultTransformer implementation. +In Java configuration, set the `resultTransformer` in the constructor's parameter. +In XML configuration, use the `result-transformer` attribute to reference your implementation. IMPORTANT: If you specify the `result-factory` or the `result-type` attribute, the `alwaysUseResultFactory` property on the underlying https://docs.spring.io/spring-integration/api/org/springframework/integration/xml/transformer/XsltPayloadTransformer.html[`XsltPayloadTransformer`] is set to `true` by the https://docs.spring.io/spring-integration/api/org/springframework/integration/xml/config/XsltPayloadTransformerParser.html[`XsltPayloadTransformerParser`]. The following example configures two XSLT transformers: -[source,xml] +[tabs] +====== +Java:: ++ +[source,java,role="primary"] +---- +@Bean +public IntegrationFlow xsltFromResourceFlow() { + return IntegrationFlow.from("withResourceIn") + .transform(new XsltPayloadTransformer( + new ClassPathResource("org/springframework/integration/xml/config/test.xsl"))) + .channel("output") + .get(); +} + +@Bean +public IntegrationFlow xsltWithTemplatesFlow(Templates templates, ResultTransformer resultTransformer) { + XsltPayloadTransformer transformer = new XsltPayloadTransformer(templates, resultTransformer); + + return IntegrationFlow.from("withTemplatesAndResultTransformerIn") + .transform(transformer) + .channel("output") + .get(); +} +---- + +XML:: ++ +[source,xml,role="secondary"] ---- ---- - +====== 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: -[source,xml] +[tabs] +====== +Java:: ++ +[source,java,role="primary"] +---- +@Bean +public IntegrationFlow xsltWithParamsFlow() { + var transformer = new XsltPayloadTransformer(new ClassPathResource("transformer.xslt")); + transformer.setXsltParamHeaders("testP*", "*testsuffix", "test1", "test2"); + + ExpressionParser parser = new SpelExpressionParser(); + Map expressions = new HashMap<>(); + expressions.put("helloParameter", new ValueExpression<>("hello")); + expressions.put("firstName", parser.parseExpression("headers.fname")); + transformer.setXslParameterMappings(expressions); + + return IntegrationFlow.from("paramHeadersComboChannel") + .transform(transformer) + .channel("output") + .get(); +} +---- + +XML:: ++ +[source,xml,role="secondary"] ---- + xslt-param-headers="testP*, *testsuffix, test1, test2"> ---- +====== 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. @@ -236,29 +405,69 @@ The `value` attribute (as with any `value` in Spring beans) lets you specify sim 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. +Starting with Spring Integration 3.0, you can now specify the transformer factory class name by setting `transformerFactoryClassName` in the constructor when using Java configuration or `transformer-factory-class` attribute when using XML configuration. -[[xml-using-result-transformers-namespace]] -== Namespace Configuration and Result Transformers +[[configuration-and-result-transformers]] +== Configuration and Result Transformers We cover using result transformers in xref:xml/transformation.adoc#xml-using-result-transformers[Using `ResultTransformer` Implementations]. -The examples in this section use XML namespace configuration to illustrate several special use cases. +The examples in this section use Java Configuration as well as XML namespace configuration to illustrate several special use cases. First, we define the `ResultTransformer`, as the following example shows: -[source,xml] +[tabs] +====== +Java:: ++ +[source,java,role="primary"] +---- +@Bean +public ResultTransformer resultToDoc() { + return new ResultToDocumentTransformer(); +} +---- + +XML:: ++ +[source,xml,role="secondary"] ---- ---- +====== 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: -[source,xml] +[tabs] +====== +Java:: ++ +[source,java,role="primary"] +---- +@Bean +public IntegrationFlow xsltWithResultTransformerFlow( + @Qualifier("resultToDoc") ResultTransformer resultTransformer) { + + var transformer = new XsltPayloadTransformer( + new ClassPathResource("noop.xslt"), + resultTransformer + ); + + return IntegrationFlow.from("in") + .transform(transformer) + .channel("fahrenheitChannel") + .get(); +} +---- + +XML:: ++ +[source,xml,role="secondary"] ---- ---- +====== 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`. @@ -280,12 +489,38 @@ 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: -[source,xml] +[tabs] +====== +Java:: ++ +[source,java,role="primary"] +---- +@Bean +public IntegrationFlow xsltWithResultTypeFlow( + @Qualifier("resultToDoc") ResultTransformer resultTransformer) { + + var transformer = new XsltPayloadTransformer( + new ClassPathResource("noop.xslt"), + resultTransformer + ); + transformer.setResultType("StringResult"); + + return IntegrationFlow.from("in") + .transform(transformer) + .channel("fahrenheitChannel") + .get(); +} +---- + +XML:: ++ +[source,xml,role="secondary"] ---- ---- +====== Because we use a `ResultFactory`, the `alwaysUseResultFactory` property of the `XsltPayloadTransformer` class is implicitly set to `true`. Consequently, the referenced `ResultToDocumentTransformer` is used. diff --git a/src/reference/antora/modules/ROOT/pages/xml/xpath-splitting.adoc b/src/reference/antora/modules/ROOT/pages/xml/xpath-splitting.adoc index 1dc8278a0e..34884498dc 100644 --- a/src/reference/antora/modules/ROOT/pages/xml/xpath-splitting.adoc +++ b/src/reference/antora/modules/ROOT/pages/xml/xpath-splitting.adoc @@ -6,10 +6,31 @@ The splitter uses the provided XPath expression to split the payload into a numb 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 XPath splitter implements `MessageHandler` and should therefore be configured in conjunction with an appropriate endpoint (see example after the following example for a simpler configuration alternative). The following example configures a bean that uses an `XPathMessageSplitter`: -[source,xml] +[tabs] +====== +Java:: ++ +[source,java,role="primary"] +---- +@Bean +public EventDrivenConsumer splittingEndpoint(SubscribableChannel orderChannel, + MessageChannel orderItemsChannel, + DocumentBuilderFactory customisedDocumentBuilder) { + + XPathMessageSplitter splitterHandler = new XPathMessageSplitter("/order/items"); + splitterHandler.setDocumentBuilder(customisedDocumentBuilder); + splitterHandler.setOutputChannel(orderItemsChannel); + + return new EventDrivenConsumer(orderChannel, splitterHandler); +} +---- + +XML:: ++ +[source,xml,role="secondary"] ---- @@ -23,10 +44,44 @@ The following example configures a bean that uses an `XPathMessageSplitter`: ---- +====== + +`XPathMessageSplitter` (when using Java configuration) or XPath splitter namespace support (when using XML configuration) lets you create a message endpoint with an input channel and output channel, as the following example shows: + +[tabs] +====== +Java:: ++ +[source,java,role="primary"] +---- +@Bean +public IntegrationFlow orderItemSplitterFlow() { + return IntegrationFlow.from("orderChannel") + .split(new XPathMessageSplitter("/order/items")) + .channel("orderItemsChannel") + .get(); +} + +@Bean +public IntegrationFlow orderItemDocumentSplitterFlow(PollableChannel orderChannelQueue) { + XPathMessageSplitter xPathMessageSplitter = new XPathMessageSplitter("/order/items"); + xPathMessageSplitter.setCreateDocuments(true); -XPath splitter namespace support lets you create a message endpoint with an input channel and output channel, as the following example shows: + org.springframework.integration.core.MessageSource messageSource = + () -> (org.springframework.messaging.Message) orderChannelQueue.receive(1000); -[source,xml] + return IntegrationFlow.from(messageSource, + c -> c.poller(Pollers.fixedRate(Duration.ofMillis(2000)).maxMessagesPerPoll(1))) + .split(xPathMessageSplitter) + .channel("orderItemsChannel") + .get(); +} + +---- + +XML:: ++ +[source,xml,role="secondary"] ---- ---- +====== 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: -[source,xml] +[tabs] +====== +Java:: ++ +[source,java,role="primary"] +---- +@Bean +public Properties outputProperties() { + Properties props = new Properties(); + // The SpEL expression from XML is replaced with a direct static import + props.setProperty(OutputKeys.OMIT_XML_DECLARATION, "yes"); + return props; +} + +@Bean +public IntegrationFlow splitterWithPropertiesFlow( + @Qualifier("outputProperties") Properties customOutputProperties) { + XPathMessageSplitter xPathMessageSplitter = new XPathMessageSplitter("/order/items"); + xPathMessageSplitter.setOutputProperties(customOutputProperties); + + return IntegrationFlow.from("input") + .split(xPathMessageSplitter) + .channel("outputProperties") + .get(); +} +---- + +XML:: ++ +[source,xml,role="secondary"] ---- yes @@ -59,6 +144,8 @@ The following example defines a property and uses it with the `output-properties ---- +====== + 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. diff --git a/src/reference/antora/modules/ROOT/pages/xmpp.adoc b/src/reference/antora/modules/ROOT/pages/xmpp.adoc index cc7d6e5b6e..4d6e0c8afc 100644 --- a/src/reference/antora/modules/ROOT/pages/xmpp.adoc +++ b/src/reference/antora/modules/ROOT/pages/xmpp.adoc @@ -38,15 +38,47 @@ compile "org.springframework.integration:spring-integration-xmpp:{project-versio ---- ====== -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: +[[configuration]] +== XMPP Configuration -[source,xml] +Spring Integration provides the ability for you to choose what method you need to take in order to configure XMPP for your application. +In the case of Java Configuration you can use the Spring's `@Configuration` to create the XMPP adapters that are necessary. +For XML the XMPP adapters come with support for a convenient namespace-based configuration. + +[tabs] +====== +Java:: ++ +[source,java,role="primary"] ---- -xmlns:int-xmpp="http://www.springframework.org/schema/integration/xmpp" +@Configuration +public class XmppOutboundConfig { + + @Bean + public ChatMessageSendingMessageHandler outboundEventAdapter(XMPPConnection testConnection) { + ChatMessageSendingMessageHandler handler = new ChatMessageSendingMessageHandler(testConnection); + handler.setOutputChannelName("outboundEventChannel"); + return handler; + } +} +---- + +XML:: ++ +[source,xml,role="secondary"] +---- + + + + + ---- +====== + [[xmpp-connection]] == XMPP Connection @@ -56,7 +88,29 @@ All XMPP adapters connected to a particular account can share this connection ob 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: -[source,xml] + +[tabs] +====== +Java:: ++ +[source,java,role="primary"] +---- +@Bean +public XmppConnectionFactoryBean myConnection() { + XmppConnectionFactoryBean factory = new XmppConnectionFactoryBean(); + factory.setUser("user"); + factory.setPassword("password"); + factory.setHost("host"); + factory.setPort(5222); + factory.setResource("theNameOfTheResource"); + factory.setSubscriptionMode("accept_all"); + return factory; +} +---- + +XML:: ++ +[source,xml,role="secondary"] ---- ---- +====== NOTE: 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. @@ -97,7 +152,30 @@ 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: -[source,xml] + +[tabs] +====== +Java:: ++ +[source,java,role="primary"] +---- +@Bean +public ChatMessageListeningEndpoint xmppInboundAdapter(XMPPConnection testConnection, + StanzaFilter stanzaFilter, MessageChannel inputChannel) { + ChatMessageListeningEndpoint endpoint = new ChatMessageListeningEndpoint(testConnection); + endpoint.setOutputChannelName("xmppInbound"); + endpoint.setPayloadExpression( + new SpelExpressionParser().parseExpression("getExtension('google:mobile:data').json") + ); + endpoint.setStanzaFilter(stanzaFilter); + endpoint.setAutoStartup(true); + return endpoint; +} +---- + +XML:: ++ +[source,xml,role="secondary"] ---- ---- +====== Along with the usual attributes (for a message channel adapter), this adapter also requires a reference to an XMPP Connection. @@ -122,27 +201,85 @@ The incoming `org.jivesoftware.smack.packet.Message` represents a root object fo This option is useful when you use xref:xmpp.adoc#xmpp-extensions[XMPP extensions]. For example, for the GCM protocol we can extract the body by using the following expression: -[source,xml] +[tabs] +====== +Java:: ++ +[source,java,role="primary"] +---- +@Bean +public ChatMessageListeningEndpoint xmppInboundAdapter(XMPPConnection testConnection, + StanzaFilter stanzaFilter) { + var parser = new SpelExpressionParser(); + + var endpoint = new ChatMessageListeningEndpoint(testConnection); + // ... + endpoint.setPayloadExpression( + parser.parseExpression("getExtension('google:mobile:data').json") + ); + //... + return endpoint; +} +---- + +XML:: ++ +[source,xml,role="secondary"] ---- + + + ---- +====== The following example extracts the body for the XHTML protocol: -[source,xml] +[tabs] +====== +Java:: ++ +[source,java,role="primary"] ---- -payload-expression="getExtension(T(org.jivesoftware.smackx.xhtmlim.packet.XHTMLExtension).NAMESPACE).bodies[0]" +endpoint.setPayloadExpression(parser.parseExpression("getExtension(T(org.jivesoftware.smackx.xhtmlim.packet.XHTMLExtension).NAMESPACE).bodies[0]")); ---- +XML:: ++ +[source,xml,role="secondary"] +---- + +---- +====== 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: -[source,xml] +[tabs] +====== +Java:: ++ +[source,java,role="primary"] +---- +endpoint.setPayloadExpression(parser.parseExpression("#extension.json")); +// or +endpoint.setPayloadExpression(parser.parseExpression("#extension.bodies[0]")); ---- + +XML:: ++ +[source,xml,role="secondary"] +---- + + ---- +====== [[xmpp-message-outbound-channel-adapter]] === Outbound Message Channel Adapter @@ -150,12 +287,29 @@ payload-expression="#extension.bodies[0]" 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. -[source,xml] +[tabs] +====== +Java:: ++ +[source,java,role="primary"] +---- +@Bean +public ChatMessageSendingMessageHandler outboundEventAdapter(XMPPConnection testConnection) { + ChatMessageSendingMessageHandler handler = new ChatMessageSendingMessageHandler(testConnection); + handler.setOutputChannelName("outboundEventChannel"); + return handler; +} +---- + +XML:: ++ +[source,xml,role="secondary"] ---- ---- +====== 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: @@ -169,12 +323,31 @@ Message xmppOutboundMsg = MessageBuilder.withPayload("Hello, XMPP!" ) You can also set the header by using the XMPP header-enricher support, as the following example shows: -[source,xml] +[tabs] +====== +Java:: ++ +[source,java,role="primary"] +---- +@Bean +@Transformer(inputChannel = "inboundChannelForEnricher", outputChannel = "outputChannelAfterEnrichment") +public HeaderEnricher xmppHeaderEnricher() { + Map> headersToAdd = new HashMap<>(); + headersToAdd.put(XmppHeaders.CHAT, + new StaticHeaderValueMessageProcessor<>("test1@example.org")); + return new HeaderEnricher(headersToAdd); +} +---- + +XML:: ++ +[source,xml,role="secondary"] ---- ---- +====== Starting with version 4.3, the packet extension support has been added to the `ChatMessageSendingMessageHandler` (the `` 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.XmlElement` (which is populated to the `org.jivesoftware.smack.packet.Message.addExtension()`) instead of `setBody()`. @@ -201,11 +374,29 @@ The payload of the message is a `org.jivesoftware.smack.packet.Presence` object 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: -[source,xml] +[tabs] +====== +Java:: ++ +[source,java,role="primary"] +---- +@Bean +public PresenceListeningEndpoint presenceInboundChannelAdapter(XMPPConnection testConnection) { + PresenceListeningEndpoint endpoint = new PresenceListeningEndpoint(testConnection); + endpoint.setOutputChannelName("outChannel"); + endpoint.setAutoStartup(false); + return endpoint; +} +---- + +XML:: ++ +[source,xml,role="secondary"] ---- ---- +====== Along with the usual attributes, this adapter requires a reference to an XMPP Connection. This adapter is event-driven and a `Lifecycle` implementation. @@ -220,16 +411,50 @@ When you send a message to the outbound presence message channel adapter, it ext 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: -[source,xml] +[tabs] +====== +Java:: ++ +[source,java,role="primary"] +---- +@Bean +public PresenceSendingMessageHandler eventOutboundPresenceChannel(XMPPConnection testConnection) { + return new PresenceSendingMessageHandler(testConnection); +} +---- + +XML:: ++ +[source,xml,role="secondary"] ---- ---- +====== 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: -[source,xml] +[tabs] +====== +Java:: ++ +[source,java,role="primary"] +---- +@Bean +public PollingConsumer pollingOutboundPresenceAdapter( + PollableChannel pollingChannel, + MessageHandler pollingOutboundPresenceHandler) { + PollingConsumer consumer = new PollingConsumer(pollingChannel, pollingOutboundPresenceHandler); + consumer.setTrigger(new PeriodicTrigger(1000)); + consumer.setMaxMessagesPerPoll(1); + return consumer; +} +---- + +XML:: ++ +[source,xml,role="secondary"] ---- ---- +====== Like its inbound counterpart, it requires a reference to an XMPP Connection. @@ -248,15 +474,43 @@ In that case, the bean with named `xmppConnection` is located and injected into Spring Integration's XMPP support is based on the Smack 4.0 API (https://www.igniterealtime.org/projects/smack/), which allows more complex configuration of the XMPP Connection object. -As xref:xmpp.adoc#xmpp-connection[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. +As xref:xmpp.adoc#xmpp-connection[stated earlier], the `XmppConnection` 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 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: -[source,xml] +[tabs] +====== +Java:: ++ +[source,java,role="primary"] +---- +@Bean +public XmppConnectionFactoryBean xmppConnection(SocketFactory socketFactory) throws Exception { + ConnectionConfiguration config = new ConnectionConfiguration("myServiceName"); + config.setSocketFactory(socketFactory); + + return new XmppConnectionFactoryBean(config); +} + +@Bean +public MessageChannel outboundEventChannel() { + return new DirectChannel(); +} + +@Bean +public ChatMessageSendingMessageHandler outboundEventAdapter(XMPPConnection xmppConnection) { + ChatMessageSendingMessageHandler handler = new ChatMessageSendingMessageHandler(xmppConnection); + handler.setOutputChannelName("outboundEventChannel"); + return handler; +} +---- + +XML:: ++ +[source,xml,role="secondary"] ---- @@ -273,6 +527,7 @@ The following example shows how to do so: 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. @@ -345,7 +600,7 @@ The XMPP specification deliberately describes only a set of core features: 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). +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 extensions they cannot understand). To solve that problem, the XMPP Standards Foundation (XSF) publishes a series of extra documents, known as https://xmpp.org/extensions/xep-0001.html[XMPP Extension Protocols] (XEPs). In general, each XEP describes a particular activity (from message formatting to file transfers, multi-user chats, and many more).