+ * Note: the {@link org.jivesoftware.smack.ChatManager}
+ * maintains a Map<String, Chat> for threads and users, where the threadID
+ * ({@link String}) is the key or the userID {@link String} is the key. This
+ * {@link java.util.Map} is a Smack-specific implementation called
+ * {@link org.jivesoftware.smack.util.collections.ReferenceMap} that removes
+ * key/values as references are dereferenced. Take care to enable this garbage
+ * collection, taking what you need from the payload and the headers and
+ * discarding as soon as possible.
+ *
+ * @author Josh Long
+ * @author Mark Fisher
+ * @see {@link org.jivesoftware.smack.ChatManager} the ChatManager class that
+ * keeps watch over all Chats between the client and any other
+ * participants.
+ * @see {@link org.springframework.integration.channel.MessageChannelTemplate}
+ * handles all interesing operations on any Spring Integration channels.
+ * @see {@link org.jivesoftware.smack.XMPPConnection} the XMPPConnection (as
+ * created by
+ * {@link org.springframework.integration.xmpp.XmppConnectionFactory}
+ */
+public class XmppMessageDrivenEndpoint extends AbstractEndpoint implements Lifecycle {
+
+ private static final Log logger = LogFactory.getLog(XmppMessageDrivenEndpoint.class);
+
+ private final MessageChannelTemplate channelTemplate = new MessageChannelTemplate();
+
+ private volatile MessageChannel requestChannel;
+
+ private volatile XMPPConnection xmppConnection;
+
+ private volatile boolean extractPayload = true;
+
+ /**
+ * This will be injected or configured via a xmpp-connection-factory element.
+ *
+ * @param xmppConnection
+ */
+ public void setXmppConnection(final XMPPConnection xmppConnection) {
+ this.xmppConnection = xmppConnection;
+ }
+
+ /**
+ * @param requestChannel the channel on which the inbound message should be sent
+ */
+ public void setRequestChannel(final MessageChannel requestChannel) {
+ this.channelTemplate.setDefaultChannel(requestChannel);
+ this.requestChannel = requestChannel;
+ }
+
+
+
+ /**
+ * Specify whether the text message body should be extracted when mapping to a
+ * Spring Integration Message payload. Otherwise, the full XMPP Message will be
+ * passed within the payload. This value is true by default.
+ */
+ public void setExtractPayload(boolean extractPayload) {
+ this.extractPayload = extractPayload;
+ }
+
+ @Override
+ protected void doStart() {
+ logger.debug("start: " + xmppConnection.isConnected() + ":" + xmppConnection.isAuthenticated());
+ }
+
+ @Override
+ protected void doStop() {
+ if (xmppConnection.isConnected()) {
+ logger.debug("shutting down " + XmppMessageDrivenEndpoint.class.getName() + ".");
+ xmppConnection.disconnect();
+ }
+ }
+
+ @Override
+ protected void onInit() throws Exception {
+ channelTemplate.afterPropertiesSet();
+ xmppConnection.addPacketListener(new PacketListener() {
+ public void processPacket(final Packet packet) {
+ org.jivesoftware.smack.packet.Message message = (org.jivesoftware.smack.packet.Message) packet;
+ forwardXmppMessage(xmppConnection.getChatManager().getThreadChat(message.getThread()), message);
+ }
+ }, null);
+ }
+
+ private void forwardXmppMessage(Chat chat, Message xmppMessage) {
+ Object payload = (this.extractPayload ? xmppMessage.getBody() : xmppMessage);
+ MessageBuilder> messageBuilder = MessageBuilder.withPayload(payload)
+ .setHeader(XmppHeaders.TYPE, xmppMessage.getType())
+ .setHeader(XmppHeaders.CHAT, chat);
+ channelTemplate.send(messageBuilder.build(), requestChannel);
+ }
+
+}
diff --git a/spring-integration-xmpp/src/main/java/org/springframework/integration/xmpp/messages/XmppMessageSendingMessageHandler.java b/spring-integration-xmpp/src/main/java/org/springframework/integration/xmpp/messages/XmppMessageSendingMessageHandler.java
new file mode 100644
index 0000000000..34e8d6eead
--- /dev/null
+++ b/spring-integration-xmpp/src/main/java/org/springframework/integration/xmpp/messages/XmppMessageSendingMessageHandler.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright 2002-2010 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.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.integration.xmpp.messages;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.jivesoftware.smack.Chat;
+import org.jivesoftware.smack.XMPPConnection;
+import org.springframework.context.Lifecycle;
+import org.springframework.integration.message.MessageHandler;
+import org.springframework.integration.xmpp.XmppHeaders;
+import org.springframework.util.Assert;
+import org.springframework.util.StringUtils;
+
+/**
+ * @author Josh Long
+ * @author Mario Gray
+ * @since 2.0
+ */
+public class XmppMessageSendingMessageHandler implements MessageHandler, Lifecycle {
+
+ private static final Log logger = LogFactory.getLog(XmppMessageSendingMessageHandler.class);
+
+ private volatile boolean running;
+ private volatile XMPPConnection xmppConnection;
+
+ public void setXmppConnection(final XMPPConnection xmppConnection) {
+ this.xmppConnection = xmppConnection;
+ }
+
+ public void handleMessage(final org.springframework.integration.core.Message> message) {
+ try {
+ // pre-reqs: user to send, string to send as msg body
+ String messageBody = null;
+ String destinationUser = null;
+ Object payload = message.getPayload();
+ if (payload instanceof String) {
+ messageBody = (String) payload;
+ }
+ destinationUser = (String) message.getHeaders().get(XmppHeaders.CHAT_TO_USER);
+ Assert.state(StringUtils.hasText(destinationUser), "the destination user must not be null");
+ Assert.state(StringUtils.hasText(messageBody), "the message body must not be null");
+ String threadId = (String) message.getHeaders().get(XmppHeaders.CHAT_THREAD_ID);
+ Chat chat = getOrCreateChatWithParticipant(destinationUser, threadId);
+ if (chat != null) {
+ chat.sendMessage(messageBody);
+ }
+ }
+ catch (Exception e) {
+ logger.debug("failed to send XMPP message", e);
+ }
+ }
+
+ public boolean isRunning() {
+ return this.running;
+ }
+
+ public void start() {
+ this.running = true;
+ }
+
+ public void stop() {
+ this.running = false;
+ if (xmppConnection.isConnected()) {
+ if (logger.isInfoEnabled()) {
+ logger.info("shutting down XMPP connection");
+ }
+ xmppConnection.disconnect();
+ }
+ }
+
+ private Chat getOrCreateChatWithParticipant(String userId, String thread) {
+ Chat chat = null;
+ if (!StringUtils.hasText(thread)) {
+ chat = xmppConnection.getChatManager().createChat(userId, null);
+ }
+ else {
+ chat = xmppConnection.getChatManager().getThreadChat(thread);
+ if (chat == null) {
+ chat = xmppConnection.getChatManager().createChat(userId, thread, null);
+ }
+ }
+ return chat;
+ }
+
+}
diff --git a/spring-integration-xmpp/src/main/java/org/springframework/integration/xmpp/presence/XmppPresenceMessageMapper.java b/spring-integration-xmpp/src/main/java/org/springframework/integration/xmpp/presence/XmppPresenceMessageMapper.java
new file mode 100644
index 0000000000..0523a2f772
--- /dev/null
+++ b/spring-integration-xmpp/src/main/java/org/springframework/integration/xmpp/presence/XmppPresenceMessageMapper.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright 2002-2010 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.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.integration.xmpp.presence;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.jivesoftware.smack.packet.Presence;
+
+import org.springframework.integration.core.Message;
+import org.springframework.integration.core.MessageHeaders;
+import org.springframework.integration.message.InboundMessageMapper;
+import org.springframework.integration.message.MessageBuilder;
+import org.springframework.integration.message.OutboundMessageMapper;
+import org.springframework.integration.xmpp.XmppHeaders;
+
+import org.springframework.util.StringUtils;
+
+
+/**
+ * Implementation of the strategy interface {@link org.springframework.integration.message.OutboundMessageMapper}. This is the hook that lets the adapter receive various payloads from
+ * components inside Spring Integration and forward them correctly as {@link org.jivesoftware.smack.packet.Presence} instances.
+ *
+ * @author Josh Long
+ * @since 2.0
+ */
+public class XmppPresenceMessageMapper implements OutboundMessageMapper
+ *
+ * @author Josh Long
+ * @since 2.0
+ */
+public class XmppRosterEventMessageDrivenEndpoint extends AbstractEndpoint implements Lifecycle {
+ private static final Log logger = LogFactory.getLog(XmppRosterEventMessageDrivenEndpoint.class);
+ private volatile MessageChannel requestChannel;
+ private volatile XMPPConnection xmppConnection;
+ private InboundMessageMapper
+ *
+ *
+ * @author Josh Long
+ * @since 2.0
+ */
+@ContextConfiguration
+@RunWith(SpringJUnit4ClassRunner.class)
+public class PresenceMessageComboTests {
+
+ @Test
+ public void run () throws Throwable {
+ Thread.sleep( 60 * 1000);
+ }
+
+}
diff --git a/spring-integration-xmpp/src/test/java/org/springframework/integration/xmpp/presence/XmppRosterEventConsumer.java b/spring-integration-xmpp/src/test/java/org/springframework/integration/xmpp/presence/XmppRosterEventConsumer.java
new file mode 100644
index 0000000000..96fd1b52db
--- /dev/null
+++ b/spring-integration-xmpp/src/test/java/org/springframework/integration/xmpp/presence/XmppRosterEventConsumer.java
@@ -0,0 +1,29 @@
+package org.springframework.integration.xmpp.presence;
+
+import org.apache.commons.lang.StringUtils;
+import org.springframework.integration.annotation.ServiceActivator;
+import org.springframework.integration.core.Message;
+import org.springframework.integration.core.MessageHeaders;
+import org.springframework.integration.xmpp.XmppHeaders;
+
+import java.util.Collection;
+
+/**
+ *
+ * This class reacts to changes in {@link org.jivesoftware.smack.packet.Presence} objects for a given account.
+ *
+ * @author Josh Long
+ * @since 2.0
+ */
+public class XmppRosterEventConsumer {
+
+ @ServiceActivator
+ public void presenceChanged ( Message> presenceEventMsg ) throws Exception {
+ System.out.println(StringUtils.repeat( "-" , 100));
+ String whosePresence = (String)presenceEventMsg.getHeaders().get( XmppHeaders.PRESENCE_FROM);
+ System.out.println( "entries affected: " + whosePresence);
+ MessageHeaders messageHeaders = presenceEventMsg.getHeaders();
+ for( String h : messageHeaders.keySet() )
+ System.out.println( String.format( "%s = %s", h, messageHeaders.get(h)));
+ }
+}
diff --git a/spring-integration-xmpp/src/test/java/org/springframework/integration/xmpp/presence/XmppRosterEventProducer.java b/spring-integration-xmpp/src/test/java/org/springframework/integration/xmpp/presence/XmppRosterEventProducer.java
new file mode 100644
index 0000000000..e398546ba5
--- /dev/null
+++ b/spring-integration-xmpp/src/test/java/org/springframework/integration/xmpp/presence/XmppRosterEventProducer.java
@@ -0,0 +1,31 @@
+package org.springframework.integration.xmpp.presence;
+
+import org.apache.commons.lang.StringUtils;
+
+import org.jivesoftware.smack.packet.Presence;
+
+import org.springframework.integration.core.Message;
+import org.springframework.integration.message.*;
+import org.springframework.integration.xmpp.XmppHeaders;
+
+
+/**
+ * This is used in {@link org.springframework.integration.xmpp.presence.OutboundXmppRosterEventsEndpointTests} to produce fake status / presence updates.
+ *
+ * @author Josh Long
+ * @since 2.0
+ */
+public class XmppRosterEventProducer implements MessageSource