36. XMPP Support

Spring Integration provides Channel Adapters for XMPP.

36.1 Introduction

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 far more applications. XMPP is used to describe a network of actors. Within that network, actors may address each other directly, as well as broadcast status changes (e.g. "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. There are many good open-source XMPP servers available. Two popular implementations are Openfire and ejabberd

Spring integration provides support for XMPP via XMPP adapters which support sending and receiving both XMPP chat messages and presence changes from other entries in your roster. 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
	http://www.springframework.org/schema/integration/xmpp/spring-integration-xmpp.xsd"

36.2 XMPP Connection

Before using inbound or outbound XMPP adapters to participate in the XMPP network, an actor must establish its XMPP connection. This connection object could be shared by all XMPP adapters connected to a particular account. Typically this requires - at a minimum -user, password, and host. To create a basic XMPP connection, you can utilize the convenience of the namespace.

<int-xmpp:xmpp-connection
    id="myConnection"
    user="user"
    password="password"
    host="host"
    port="port"
    resource="theNameOfTheResource"
    subscription-mode="accept_all"/>
[Note]Note

For added convenience you can rely on the default naming convention and omit the id attribute. The default name xmppConnection will be used for this connection bean.

If the XMPP Connection goes stale, reconnection attempts will be made with an automatic login as long as the previous connection state was logged (authenticated). We also register a ConnectionListener which will log connection events if the DEBUG logging level is enabled.

The subscription-mode initiates the Roster listener to deal with incoming subscriptions from other users. This functionality isn’t always available for the target XMPP servers. For example GCM fully disables it. To switch off the Roster listener for subscriptions you should configure it with an empty string when using XML configuration: subscription-mode="", or with XmppConnectionFactoryBean.setSubscriptionMode(null) when using Java Configuration.

36.3 XMPP Messages

36.3.1 Inbound Message Channel Adapter

The Spring Integration adapters support receiving chat messages from other users in the system. To do this, 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. Configuration support for the XMPP Inbound Message Channel Adapter is provided via the inbound-channel-adapter element.

<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"/>

As you can see amongst the usual attributes this adapter also requires a reference to an XMPP Connection.

It is also important to mention that the XMPP inbound adapter is an event driven adapter and a Lifecycle implementation. When started it will register a PacketListener that will listen for incoming XMPP Chat Messages. It forwards any received messages to the underlying adapter which will convert them to Spring Integration Messages and send them to the specified channel. It will unregister the PacketListener when it is stopped.

Starting with version 4.3 the ChatMessageListeningEndpoint (and its <int-xmpp:inbound-channel-adapter>) supports a org.jivesoftware.smack.filter.StanzaFilter injection to be registered on the provided XMPPConnection together with an internal StanzaListener implementation. See their JavaDocs for more information.

Also with the version 4.3 the payload-expression has been introduced for the ChatMessageListeningEndpoint. The incoming org.jivesoftware.smack.packet.Message represents a root object of evaluation context. This option is useful in case of Section 36.7, “XMPP Extensions”. For example, for the GCM protocol we can extract the body using expression:

payload-expression="getExtension('google:mobile:data').json"

for the XHTML protocol:

payload-expression="getExtension(T(org.jivesoftware.smackx.xhtmlim.packet.XHTMLExtension).NAMESPACE).bodies[0]"

To simplify the access to the Extension in the XMPP Message, the extension variable is added into the EvaluationContext. Note, it is done only when one and only one Extension is present in the Message. The samples above with the namespace manipulations can be simplified to something like:

 ----
 payload-expression="#extension.json"
 payload-expression="#extension.bodies[0]"
 ----
[Note]Note

The extract-payload option has been deprecated in favor of the new payload-expression one.

36.3.2 Outbound Message Channel Adapter

You may also send chat messages to other users on XMPP using the Outbound Message Channel Adapter. Configuration support for the XMPP Outbound Message Channel Adapter is provided via the outbound-channel-adapter element.

<int-xmpp:outbound-channel-adapter id="outboundEventAdapter"
						channel="outboundEventChannel"
						xmpp-connection="testConnection"/>

The adapter expects as its input - 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 might use the following Java code:

Message<String> xmppOutboundMsg = MessageBuilder.withPayload("Hello, XMPP!" )
						.setHeader(XmppHeaders.CHAT_TO, "userhandle")
						.build();

Another mechanism of setting the header is by using the XMPP header-enricher support. Here is an example.

<int-xmpp:header-enricher input-channel="input" output-channel="output">
	<int-xmpp:chat-to value="test1@example.org"/>
</int-xmpp:header-enricher>

Starting with version 4.3 the packet extension support has been added to the ChatMessageSendingMessageHandler (<int-xmpp:outbound-channel-adapter>). Alongside with the regular String and org.jivesoftware.smack.packet.Message payload, now you can send a message with a payload as a org.jivesoftware.smack.packet.ExtensionElement which is populated to the org.jivesoftware.smack.packet.Message.addExtension() instead of setBody(). For the convenience an extension-provider option has been added for the ChatMessageSendingMessageHandler to allow to inject org.jivesoftware.smack.provider.ExtensionElementProvider, which builds an ExtensionElement against the payload at runtime. For this case the payload must be String in JSON or XML format depending of the XEP protocol.

36.4 XMPP Presence

XMPP also supports broadcasting state. You can use this capability 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 then set an away message, and everybody who has you on their roster sees your icon or username change to reflect this new state, and additionally might see your new "away" message. If you would like to receive notification, or notify others, of state changes, you can use Spring Integration’s "presence" adapters.

36.4.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 happen to be 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 will be a org.jivesoftware.smack.packet.Presence object (see https://www.igniterealtime.org/builds/smack/docs/latest/javadoc/org/jivesoftware/smack/packet/Presence.html).

Configuration support for the XMPP Inbound Presence Message Channel Adapter is provided via the presence-inbound-channel-adapter element.

<int-xmpp:presence-inbound-channel-adapter channel="outChannel"
		xmpp-connection="testConnection" auto-startup="false"/>

As you can see amongst the usual attributes this adapter also requires a reference to an XMPP Connection. It is also important to mention that this adapter is an event driven adapter and a Lifecycle implementation. It will register a RosterListener when started and will unregister that RosterListener when stopped.

36.4.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.

Configuration support for the XMPP Outbound Presence Message Channel Adapter is provided via the presence-outbound-channel-adapter element.

<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.

<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.

[Note]Note

If you are relying 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 may omit the xmpp-connection attribute. In that case, the bean with the name xmppConnection will be located and injected into the adapter.

36.5 Advanced Configuration

Since Spring Integration XMPP support is based on the Smack 4.0 API (http://www.igniterealtime.org/projects/smack/), it is important to know a few details related to more complex configuration of the XMPP Connection object.

As stated earlier the xmpp-connection namespace support is designed to simplify basic connection configuration and only supports a few common configuration attributes. However, the org.jivesoftware.smack.ConnectionConfiguration object defines about 20 attributes, and there is no real value of adding namespace support for all of them. So, for more complex connection configurations, simply configure an instance of our XmppConnectionFactoryBean as a regular bean, and inject a org.jivesoftware.smack.ConnectionConfiguration as a constructor argument to that FactoryBean. Every property you need, can be specified directly on that ConnectionConfiguration instance (a bean definition with the p namespace would work well). This way SSL, or any other attributes, could be set directly. Here’s an example:

<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"/>

Another important aspect of the Smack API is static initializers. For more complex cases (e.g., registering a SASL Mechanism), you may need to execute certain static initializers. One of those static initializers is SASLAuthentication, which allows you to register supported SASL mechanisms. For that level of complexity, we would recommend Spring JavaConfig-style of the XMPP Connection configuration. Then, you can configure the entire component through Java code and execute all other necessary Java code including static initializers at the appropriate time.

@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 the JavaConfig style of Application Context configuration, refer to the following section in the Spring Reference Manual.

36.6 XMPP Message Headers

The Spring Integration XMPP Adapters will map standard XMPP properties automatically. These properties will be copied by default to and from Spring Integration MessageHeaders using the DefaultXmppHeaderMapper.

Any user-defined headers will NOT be copied to or from an XMPP Message, unless explicitly specified by the requestHeaderNames and/or replyHeaderNames properties of the DefaultXmppHeaderMapper.

[Tip]Tip

When mapping user-defined headers, the values can also contain simple wildcard patterns (e.g. "foo*" or "*foo") to be matched.

Starting with version 4.1, the AbstractHeaderMapper (a DefaultXmppHeaderMapper superclass) allows the NON_STANDARD_HEADERS token to be configured for the requestHeaderNames property (in addition to existing STANDARD_REQUEST_HEADERS) to map all user-defined headers.

Class org.springframework.xmpp.XmppHeaders identifies the default headers that will be used by the DefaultXmppHeaderMapper:

  • xmpp_from
  • xmpp_subject
  • xmpp_thread
  • xmpp_to
  • xmpp_type

Starting with version 4.3, patterns in the header mappings can be negated by preceding the pattern with !. Negated patterns get priority, so a list such as STANDARD_REQUEST_HEADERS,foo,ba*,!bar,!baz,qux,!foo will NOT map foo (nor bar nor baz); the standard headers plus bad, qux will be mapped.

[Important]Important

If you have a user defined header that begins with ! that you do wish to map, you need to escape it with \ thus: STANDARD_REQUEST_HEADERS,\!myBangHeader and it WILL be mapped.

36.7 XMPP Extensions

The XMPP protocol stands for eXstensible Messaging and Presence Protocol. The "extensible" part is important. 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. This is important because the XMPP specification deliberately describes only a set of core things like:

  • How a client connects to a server
  • Encryption (SSL/TLS)
  • Authentication
  • How servers can communicate with each other to relay messages
  • and 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. But that’s not the end.

For example, perhaps you decide that you want to include formatting in a message (bold, italic, etc.) 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 as you, no other software will be able interpret it (they will just ignore namespaces they don’t understand).

So 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), and they provide a standard format for everyone to use for that activity.

The Smack API provides many XEP implementations with its extensions and experimental projects. And starting with Spring Integration version 4.3 any XEP can be use with the existing XMPP channel adapters.

To be able to process XEPs or any other custom XMPP extensions, the Smack’s ProviderManager pre-configuration must be provided. It can be done via direct usage from the static Java code:

ProviderManager.addIQProvider("element", "namespace", new MyIQProvider());
ProviderManager.addExtensionProvider("element", "namespace", new MyExtProvider());

or via .providers configuration file in the specific instance and JVM argument:

-Dsmack.provider.file=file:///c:/my/provider/mycustom.providers

where mycustom.providers might be like this:

<?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>http://jabber.org/protocol/disco#items</namespace>
    <className>org.jivesoftware.smackx.provider.DiscoverItemsProvider</className>
</iqProvider>

<extensionProvider>
    <elementName>subscription</elementName>
    <namespace>http://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 provides the particular org.jivesoftware.smackx.gcm.provider.GcmExtensionProvider for that and registers that by default with the smack-experimental jar in the classpath using experimental.providers resource:

<!-- GCM JSON payload -->
<extensionProvider>
    <elementName>gcm</elementName>
    <namespace>google:mobile:data</namespace>
    <className>org.jivesoftware.smackx.gcm.provider.GcmExtensionProvider</className>
</extensionProvider>

Also the GcmPacketExtension is present for the target messaging protocol to parse incoming packets and build outgoing:

GcmPacketExtension gcmExtension = (GcmPacketExtension) xmppMessage.getExtension(GcmPacketExtension.NAMESPACE);
String message = gcmExtension.getJson());
GcmPacketExtension packetExtension = new GcmPacketExtension(gcmJson);
Message smackMessage = new Message();
smackMessage.addExtension(packetExtension);

See Section 36.3.1, “Inbound Message Channel Adapter” and Section 36.3.2, “Outbound Message Channel Adapter” above for more information.