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.2.0.M4</version>
</dependency>
compile "org.springframework.integration:spring-integration-xmpp:5.2.0.M4"
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"
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.
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.
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]"
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 value="[email protected]"/>
</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.
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.
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 https://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.
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.
|
Advanced Configuration
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 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.
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.
|
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.