diff --git a/spring-integration-xmpp/src/main/java/org/springframework/integration/xmpp/config/ChatMessageOutboundChannelAdapterParser.java b/spring-integration-xmpp/src/main/java/org/springframework/integration/xmpp/config/ChatMessageOutboundChannelAdapterParser.java
index 774e392b65..5a630413fa 100644
--- a/spring-integration-xmpp/src/main/java/org/springframework/integration/xmpp/config/ChatMessageOutboundChannelAdapterParser.java
+++ b/spring-integration-xmpp/src/main/java/org/springframework/integration/xmpp/config/ChatMessageOutboundChannelAdapterParser.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2010 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,17 +17,38 @@
package org.springframework.integration.xmpp.config;
+import org.w3c.dom.Element;
+
+import org.springframework.beans.factory.config.RuntimeBeanReference;
+import org.springframework.beans.factory.support.AbstractBeanDefinition;
+import org.springframework.beans.factory.xml.ParserContext;
+import org.springframework.integration.xmpp.outbound.ChatMessageSendingMessageHandler;
+import org.springframework.util.StringUtils;
+
/**
* Parser for the XMPP 'outbound-channel-adapter' element
*
* @author Oleg Zhurakousky
+ * @author Artem Bilan
+ *
* @since 2.0
*/
public class ChatMessageOutboundChannelAdapterParser extends AbstractXmppOutboundChannelAdapterParser {
@Override
protected String getHandlerClassName() {
- return "org.springframework.integration.xmpp.outbound.ChatMessageSendingMessageHandler";
+ return ChatMessageSendingMessageHandler.class.getName();
+ }
+
+ @Override
+ protected AbstractBeanDefinition parseConsumer(Element element, ParserContext parserContext) {
+ AbstractBeanDefinition beanDefinition = super.parseConsumer(element, parserContext);
+ String extensionProvider = element.getAttribute("extension-provider");
+ if (StringUtils.hasText(extensionProvider)) {
+ beanDefinition.getPropertyValues()
+ .addPropertyValue("extensionProvider", new RuntimeBeanReference(extensionProvider));
+ }
+ return beanDefinition;
}
}
diff --git a/spring-integration-xmpp/src/main/java/org/springframework/integration/xmpp/outbound/ChatMessageSendingMessageHandler.java b/spring-integration-xmpp/src/main/java/org/springframework/integration/xmpp/outbound/ChatMessageSendingMessageHandler.java
index 78d9674f1d..9d6246e5cc 100644
--- a/spring-integration-xmpp/src/main/java/org/springframework/integration/xmpp/outbound/ChatMessageSendingMessageHandler.java
+++ b/spring-integration-xmpp/src/main/java/org/springframework/integration/xmpp/outbound/ChatMessageSendingMessageHandler.java
@@ -16,8 +16,15 @@
package org.springframework.integration.xmpp.outbound;
+import java.io.StringReader;
+import java.util.regex.Pattern;
+
import org.jivesoftware.smack.AbstractXMPPConnection;
import org.jivesoftware.smack.XMPPConnection;
+import org.jivesoftware.smack.packet.ExtensionElement;
+import org.jivesoftware.smack.provider.ExtensionElementProvider;
+import org.jivesoftware.smack.util.PacketParserUtils;
+import org.xmlpull.v1.XmlPullParser;
import org.springframework.integration.xmpp.XmppHeaders;
import org.springframework.integration.xmpp.core.AbstractXmppConnectionAwareMessageHandler;
@@ -40,8 +47,11 @@ import org.springframework.util.StringUtils;
*/
public class ChatMessageSendingMessageHandler extends AbstractXmppConnectionAwareMessageHandler {
+ private static final Pattern xmlPattern = Pattern.compile("<(\\S[^>\\s]*)[^>]*>[^<]*\\1>");
+
private volatile XmppHeaderMapper headerMapper = new DefaultXmppHeaderMapper();
+ private ExtensionElementProvider extends ExtensionElement> extensionProvider;
public ChatMessageSendingMessageHandler() {
super();
@@ -56,6 +66,17 @@ public class ChatMessageSendingMessageHandler extends AbstractXmppConnectionAwar
this.headerMapper = headerMapper;
}
+ /**
+ * Specify an {@link ExtensionElementProvider} to build an {@link ExtensionElement}
+ * for the {@link org.jivesoftware.smack.packet.Message#addExtension(ExtensionElement)}
+ * instead of {@code body}.
+ * @param extensionProvider the {@link ExtensionElementProvider} to use.
+ * @since 4.3
+ */
+ public void setExtensionProvider(ExtensionElementProvider extends ExtensionElement> extensionProvider) {
+ this.extensionProvider = extensionProvider;
+ }
+
@Override
public String getComponentType() {
return "xmpp:outbound-channel-adapter";
@@ -64,26 +85,50 @@ public class ChatMessageSendingMessageHandler extends AbstractXmppConnectionAwar
@Override
protected void handleMessageInternal(Message> message) throws Exception {
Assert.isTrue(this.initialized, getComponentName() + "#" + this.getComponentType() + " must be initialized");
- Object messageBody = message.getPayload();
+ Object payload = message.getPayload();
org.jivesoftware.smack.packet.Message xmppMessage = null;
- if (messageBody instanceof org.jivesoftware.smack.packet.Message) {
- xmppMessage = (org.jivesoftware.smack.packet.Message) messageBody;
+ if (payload instanceof org.jivesoftware.smack.packet.Message) {
+ xmppMessage = (org.jivesoftware.smack.packet.Message) payload;
}
- else if (messageBody instanceof String) {
+ else {
String to = message.getHeaders().get(XmppHeaders.TO, String.class);
Assert.state(StringUtils.hasText(to), "The '" + XmppHeaders.TO + "' header must not be null");
xmppMessage = new org.jivesoftware.smack.packet.Message(to);
- if (this.headerMapper != null) {
- this.headerMapper.fromHeadersToRequest(message.getHeaders(), xmppMessage);
+
+ if (payload instanceof ExtensionElement) {
+ xmppMessage.addExtension((ExtensionElement) payload);
+ }
+ else if (payload instanceof String) {
+ if (this.extensionProvider != null) {
+ String data = (String) payload;
+ if (!xmlPattern.matcher(data.trim()).matches()) {
+ // Since XMPP Extension parsers deal only with XML content,
+ // add an arbitrary tag that is removed by the extension parser,
+ // if the target content isn't XML.
+ data = "" + data + "";
+ }
+ XmlPullParser xmlPullParser = PacketParserUtils.newXmppParser(new StringReader(data));
+ xmlPullParser.next();
+ ExtensionElement extension = this.extensionProvider.parse(xmlPullParser);
+ xmppMessage.addExtension(extension);
+ }
+ else {
+ xmppMessage.setBody((String) payload);
+ }
+ }
+ else {
+ throw new MessageHandlingException(message,
+ "Only payloads of type java.lang.String, org.jivesoftware.smack.packet.Message " +
+ "or org.jivesoftware.smack.packet.ExtensionElement " +
+ "are supported. Received [" + payload.getClass().getName() +
+ "]. Consider adding a Transformer prior to this adapter.");
}
- xmppMessage.setBody((String) messageBody);
}
- else {
- throw new MessageHandlingException(message,
- "Only payloads of type java.lang.String or org.jivesoftware.smack.packet.Message " +
- "are supported. Received [" + messageBody.getClass().getName() +
- "]. Consider adding a Transformer prior to this adapter.");
+
+ if (this.headerMapper != null) {
+ this.headerMapper.fromHeadersToRequest(message.getHeaders(), xmppMessage);
}
+
if (!this.xmppConnection.isConnected() && this.xmppConnection instanceof AbstractXMPPConnection) {
((AbstractXMPPConnection) this.xmppConnection).connect();
}
diff --git a/spring-integration-xmpp/src/main/resources/org/springframework/integration/xmpp/config/spring-integration-xmpp-4.3.xsd b/spring-integration-xmpp/src/main/resources/org/springframework/integration/xmpp/config/spring-integration-xmpp-4.3.xsd
index 2123abe4b5..e2d11c5022 100644
--- a/spring-integration-xmpp/src/main/resources/org/springframework/integration/xmpp/config/spring-integration-xmpp-4.3.xsd
+++ b/spring-integration-xmpp/src/main/resources/org/springframework/integration/xmpp/config/spring-integration-xmpp-4.3.xsd
@@ -160,7 +160,21 @@
-
+
+
+
+
+
+
+
+
+
+ Reference to org.jivesoftware.smack.provider.ExtensionElementProvider bean.
+
+
+
+
diff --git a/spring-integration-xmpp/src/test/java/org/springframework/integration/xmpp/config/ChatMessageOutboundChannelAdapterParserTests-context.xml b/spring-integration-xmpp/src/test/java/org/springframework/integration/xmpp/config/ChatMessageOutboundChannelAdapterParserTests-context.xml
index 0f991cb123..5b35e3a84c 100644
--- a/spring-integration-xmpp/src/test/java/org/springframework/integration/xmpp/config/ChatMessageOutboundChannelAdapterParserTests-context.xml
+++ b/spring-integration-xmpp/src/test/java/org/springframework/integration/xmpp/config/ChatMessageOutboundChannelAdapterParserTests-context.xml
@@ -13,10 +13,13 @@
+
+
+ mapped-request-headers="foo*, bar*"
+ extension-provider="testExtensionProvider"/>
diff --git a/spring-integration-xmpp/src/test/java/org/springframework/integration/xmpp/config/ChatMessageOutboundChannelAdapterParserTests.java b/spring-integration-xmpp/src/test/java/org/springframework/integration/xmpp/config/ChatMessageOutboundChannelAdapterParserTests.java
index f5eb727e84..7993882186 100644
--- a/spring-integration-xmpp/src/test/java/org/springframework/integration/xmpp/config/ChatMessageOutboundChannelAdapterParserTests.java
+++ b/spring-integration-xmpp/src/test/java/org/springframework/integration/xmpp/config/ChatMessageOutboundChannelAdapterParserTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,12 +17,14 @@
package org.springframework.integration.xmpp.config;
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import org.jivesoftware.smack.XMPPConnection;
+import org.jivesoftware.smack.provider.ExtensionElementProvider;
import org.jivesoftware.smackx.jiveproperties.JivePropertiesManager;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -32,21 +34,21 @@ import org.mockito.stubbing.Answer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
-import org.springframework.integration.mapping.AbstractHeaderMapper;
-import org.springframework.messaging.Message;
-import org.springframework.messaging.MessageChannel;
import org.springframework.integration.channel.QueueChannel;
-import org.springframework.messaging.MessageHandler;
-import org.springframework.messaging.SubscribableChannel;
import org.springframework.integration.endpoint.EventDrivenConsumer;
import org.springframework.integration.endpoint.PollingConsumer;
import org.springframework.integration.handler.advice.AbstractRequestHandlerAdvice;
-import org.springframework.messaging.support.GenericMessage;
+import org.springframework.integration.mapping.AbstractHeaderMapper;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.integration.test.util.TestUtils;
import org.springframework.integration.xmpp.XmppHeaders;
import org.springframework.integration.xmpp.support.DefaultXmppHeaderMapper;
import org.springframework.integration.xmpp.support.XmppHeaderMapper;
+import org.springframework.messaging.Message;
+import org.springframework.messaging.MessageChannel;
+import org.springframework.messaging.MessageHandler;
+import org.springframework.messaging.SubscribableChannel;
+import org.springframework.messaging.support.GenericMessage;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@@ -67,6 +69,9 @@ public class ChatMessageOutboundChannelAdapterParserTests {
@Autowired
private XmppHeaderMapper headerMapper;
+ @Autowired
+ private ExtensionElementProvider> extensionElementProvider;
+
private static volatile int adviceCalled;
@Test
@@ -107,6 +112,11 @@ public class ChatMessageOutboundChannelAdapterParserTests {
assertFalse(requestHeaderMatcher.matchHeader("biz"));
assertFalse(requestHeaderMatcher.matchHeader("else"));
assertTrue(eventConsumer instanceof EventDrivenConsumer);
+
+ MessageHandler outboundEventAdapterHandle =
+ context.getBean("outboundEventAdapter.handler", MessageHandler.class);
+ assertSame(this.extensionElementProvider,
+ TestUtils.getPropertyValue(outboundEventAdapterHandle, "extensionProvider"));
}
@SuppressWarnings("rawtypes")
diff --git a/spring-integration-xmpp/src/test/java/org/springframework/integration/xmpp/outbound/ChatMessageSendingMessageHandlerTests.java b/spring-integration-xmpp/src/test/java/org/springframework/integration/xmpp/outbound/ChatMessageSendingMessageHandlerTests.java
index 3f9461e5d4..4a1c0c6b50 100644
--- a/spring-integration-xmpp/src/test/java/org/springframework/integration/xmpp/outbound/ChatMessageSendingMessageHandlerTests.java
+++ b/spring-integration-xmpp/src/test/java/org/springframework/integration/xmpp/outbound/ChatMessageSendingMessageHandlerTests.java
@@ -16,14 +16,21 @@
package org.springframework.integration.xmpp.outbound;
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import org.jivesoftware.smack.XMPPConnection;
+import org.jivesoftware.smackx.gcm.packet.GcmPacketExtension;
+import org.jivesoftware.smackx.gcm.provider.GcmExtensionProvider;
import org.junit.Test;
+import org.mockito.ArgumentCaptor;
import org.mockito.ArgumentMatcher;
import org.mockito.Mockito;
@@ -46,7 +53,7 @@ public class ChatMessageSendingMessageHandlerTests {
@Test
- public void validateMessagePostAsString() throws Exception {
+ public void testSendMessages() throws Exception {
XMPPConnection connection = mock(XMPPConnection.class);
ChatMessageSendingMessageHandler handler = new ChatMessageSendingMessageHandler(connection);
handler.setBeanFactory(mock(BeanFactory.class));
@@ -92,6 +99,28 @@ public class ChatMessageSendingMessageHandlerTests {
// in threaded conversation we need to look for existing chat
verify(connection, times(1)).sendStanza(Mockito.argThat(new EqualSmackMessageWithThreadId()));
+
+ reset(connection);
+ final String json = "{\"foo\": \"bar\"}";
+ message = MessageBuilder.withPayload(new GcmPacketExtension(json))
+ .setHeader(XmppHeaders.TO, "kermit@frog.com")
+ .build();
+ handler.handleMessage(message);
+
+ class EqualExtension extends ArgumentMatcher {
+
+ @Override
+ public boolean matches(Object msg) {
+ org.jivesoftware.smack.packet.Message smackMessage = (org.jivesoftware.smack.packet.Message) msg;
+ boolean bodyMatches = smackMessage.getBody() == null;
+ boolean toMatches = smackMessage.getTo().equals("kermit@frog.com");
+ GcmPacketExtension gcmPacketExtension = GcmPacketExtension.from(smackMessage);
+ boolean jsonMatches = gcmPacketExtension != null && gcmPacketExtension.getJson().equals(json);
+ return bodyMatches & toMatches & jsonMatches;
+ }
+ }
+
+ verify(connection, times(1)).sendStanza(Mockito.argThat(new EqualExtension()));
}
@Test
@@ -124,6 +153,39 @@ public class ChatMessageSendingMessageHandlerTests {
verify(connection, times(1)).sendStanza(smackMessage);
}
+ @Test
+ public void testExtensionProvider() throws Exception {
+ XMPPConnection connection = mock(XMPPConnection.class);
+ ChatMessageSendingMessageHandler handler = new ChatMessageSendingMessageHandler(connection);
+ GcmExtensionProvider extensionElementProvider = spy(new GcmExtensionProvider());
+ handler.setExtensionProvider(extensionElementProvider);
+ handler.setBeanFactory(mock(BeanFactory.class));
+ handler.afterPropertiesSet();
+
+ final String json = "{\"foo\": \"bar\"}";
+ Message> message = MessageBuilder.withPayload(" " + json + " ")
+ .setHeader(XmppHeaders.TO, "kermit@frog.com")
+ .build();
+
+ handler.handleMessage(message);
+
+ ArgumentCaptor argumentCaptor =
+ ArgumentCaptor.forClass(org.jivesoftware.smack.packet.Message.class);
+
+ verify(connection).sendStanza(argumentCaptor.capture());
+
+ org.jivesoftware.smack.packet.Message smackMessage = argumentCaptor.getValue();
+
+ assertNull(smackMessage.getBody());
+ assertEquals("kermit@frog.com", smackMessage.getTo());
+ GcmPacketExtension gcmPacketExtension = GcmPacketExtension.from(smackMessage);
+ assertNotNull(gcmPacketExtension);
+ assertEquals(json, gcmPacketExtension.getJson());
+
+ verify(extensionElementProvider).from(eq(json));
+ }
+
+
@Test(expected = MessageHandlingException.class)
public void validateFailureNoChatToUser() throws Exception {
ChatMessageSendingMessageHandler handler = new ChatMessageSendingMessageHandler(mock(XMPPConnection.class));
diff --git a/src/reference/asciidoc/xmpp.adoc b/src/reference/asciidoc/xmpp.adoc
index a0abbfbb97..f23a1170bb 100644
--- a/src/reference/asciidoc/xmpp.adoc
+++ b/src/reference/asciidoc/xmpp.adoc
@@ -136,7 +136,8 @@ Configuration support for the XMPP _Outbound Message Channel Adapter_ is provide
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.
+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:
[source,java]
----
@@ -155,6 +156,16 @@ Here is an example.
----
+Starting with _version 4.3_ the packet extension support has been added to the `ChatMessageSendingMessageHandler`
+(``).
+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.
+
[[xmpp-presence]]
=== XMPP Presence