SMACK-761: Migrate the ChatState package to chat2 API

This commit is contained in:
Paul Schaub 2018-03-08 12:07:12 +01:00
parent 2e4ce965cd
commit afeafd2336
Signed by: vanitasvitae
GPG Key ID: 62BEE9264BF17311
5 changed files with 210 additions and 46 deletions

View File

@ -1,6 +1,6 @@
/** /**
* *
* Copyright 2003-2007 Jive Software. * Copyright 2003-2007 Jive Software, 2018 Paul Schaub.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -17,15 +17,16 @@
package org.jivesoftware.smackx.chatstates; package org.jivesoftware.smackx.chatstates;
import org.jivesoftware.smack.chat.ChatMessageListener; import org.jivesoftware.smack.chat2.Chat;
import org.jivesoftware.smack.packet.Message; import org.jivesoftware.smack.packet.Message;
/** /**
* Events for when the state of a user in a chat changes. * Events for when the state of a user in a chat changes.
* *
* @author Alexander Wenckus * @author Alexander Wenckus
* @author Paul Schaub
*/ */
public interface ChatStateListener extends ChatMessageListener { public interface ChatStateListener {
/** /**
* Fired when the state of a chat with another user changes. * Fired when the state of a chat with another user changes.
@ -34,7 +35,5 @@ public interface ChatStateListener extends ChatMessageListener {
* @param state the new state of the participant. * @param state the new state of the participant.
* @param message the message carrying the chat state. * @param message the message carrying the chat state.
*/ */
// TODO Migrate to new chat2 API on Smack 4.3. void stateChanged(Chat chat, ChatState state, Message message);
@SuppressWarnings("deprecation")
void stateChanged(org.jivesoftware.smack.chat.Chat chat, ChatState state, Message message);
} }

View File

@ -1,6 +1,6 @@
/** /**
* *
* Copyright 2003-2007 Jive Software. * Copyright 2003-2007 Jive Software, 2018 Paul Schaub.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -17,24 +17,33 @@
package org.jivesoftware.smackx.chatstates; package org.jivesoftware.smackx.chatstates;
import java.util.HashSet;
import java.util.Map; import java.util.Map;
import java.util.Set;
import java.util.WeakHashMap; import java.util.WeakHashMap;
import org.jivesoftware.smack.Manager; import org.jivesoftware.smack.Manager;
import org.jivesoftware.smack.MessageListener;
import org.jivesoftware.smack.SmackException.NotConnectedException; import org.jivesoftware.smack.SmackException.NotConnectedException;
import org.jivesoftware.smack.StanzaListener;
import org.jivesoftware.smack.XMPPConnection; import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.chat.ChatManagerListener; import org.jivesoftware.smack.chat2.Chat;
import org.jivesoftware.smack.chat.ChatMessageListener; import org.jivesoftware.smack.chat2.ChatManager;
import org.jivesoftware.smack.chat2.OutgoingChatMessageListener;
import org.jivesoftware.smack.filter.AndFilter;
import org.jivesoftware.smack.filter.FromTypeFilter;
import org.jivesoftware.smack.filter.MessageTypeFilter;
import org.jivesoftware.smack.filter.NotFilter; import org.jivesoftware.smack.filter.NotFilter;
import org.jivesoftware.smack.filter.StanzaExtensionFilter; import org.jivesoftware.smack.filter.StanzaExtensionFilter;
import org.jivesoftware.smack.filter.StanzaFilter; import org.jivesoftware.smack.filter.StanzaFilter;
import org.jivesoftware.smack.packet.ExtensionElement; import org.jivesoftware.smack.packet.ExtensionElement;
import org.jivesoftware.smack.packet.Message; import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smack.packet.Stanza;
import org.jivesoftware.smackx.chatstates.packet.ChatStateExtension; import org.jivesoftware.smackx.chatstates.packet.ChatStateExtension;
import org.jivesoftware.smackx.disco.ServiceDiscoveryManager; import org.jivesoftware.smackx.disco.ServiceDiscoveryManager;
import org.jxmpp.jid.EntityBareJid;
import org.jxmpp.jid.EntityFullJid;
/** /**
* Handles chat state for all chats on a particular XMPPConnection. This class manages both the * Handles chat state for all chats on a particular XMPPConnection. This class manages both the
* stanza(/packet) extensions and the disco response necessary for compliance with * stanza(/packet) extensions and the disco response necessary for compliance with
@ -45,17 +54,39 @@ import org.jivesoftware.smackx.disco.ServiceDiscoveryManager;
* If this does not occur you will not receive the update notifications. * If this does not occur you will not receive the update notifications.
* *
* @author Alexander Wenckus * @author Alexander Wenckus
* @author Paul Schaub
* @see org.jivesoftware.smackx.chatstates.ChatState * @see org.jivesoftware.smackx.chatstates.ChatState
* @see org.jivesoftware.smackx.chatstates.packet.ChatStateExtension * @see org.jivesoftware.smackx.chatstates.packet.ChatStateExtension
*/ */
// TODO Migrate to new chat2 API on Smack 4.3.
@SuppressWarnings("deprecation")
public final class ChatStateManager extends Manager { public final class ChatStateManager extends Manager {
public static final String NAMESPACE = "http://jabber.org/protocol/chatstates"; public static final String NAMESPACE = "http://jabber.org/protocol/chatstates";
private static final Map<XMPPConnection, ChatStateManager> INSTANCES = new WeakHashMap<>(); private static final Map<XMPPConnection, ChatStateManager> INSTANCES = new WeakHashMap<>();
private static final StanzaFilter filter = new NotFilter(new StanzaExtensionFilter(NAMESPACE)); private static final StanzaFilter filter = new NotFilter(new StanzaExtensionFilter(NAMESPACE));
private static final StanzaFilter INCOMING_MESSAGE_FILTER =
new AndFilter(MessageTypeFilter.NORMAL_OR_CHAT, FromTypeFilter.ENTITY_FULL_JID);
/**
* Message listener, that appends a ChatStateExtension to outgoing messages
*/
private final OutgoingMessageInterceptor outgoingInterceptor = new OutgoingMessageInterceptor();
/**
* Message listener, that triggers registered ChatStateListeners for incoming chat messages with ChatStateExtensions.
*/
private final IncomingMessageInterceptor incomingInterceptor = new IncomingMessageInterceptor();
/**
* Registered ChatStateListeners
*/
private final Set<ChatStateListener> chatStateListeners = new HashSet<>();
/**
* Maps chat to last chat state.
*/
private final Map<Chat, ChatState> chatStates = new WeakHashMap<>();
/** /**
* Returns the ChatStateManager related to the XMPPConnection and it will create one if it does * Returns the ChatStateManager related to the XMPPConnection and it will create one if it does
@ -72,27 +103,43 @@ public final class ChatStateManager extends Manager {
return manager; return manager;
} }
private final OutgoingMessageInterceptor outgoingInterceptor = new OutgoingMessageInterceptor();
private final IncomingMessageInterceptor incomingInterceptor = new IncomingMessageInterceptor();
/** /**
* Maps chat to last chat state. * Private constructor to create a new ChatStateManager.
* This adds ChatMessageListeners as interceptors to the connection and adds the namespace to the disco features.
*
* @param connection xmpp connection
*/ */
private final Map<org.jivesoftware.smack.chat.Chat, ChatState> chatStates = new WeakHashMap<>();
private final org.jivesoftware.smack.chat.ChatManager chatManager;
private ChatStateManager(XMPPConnection connection) { private ChatStateManager(XMPPConnection connection) {
super(connection); super(connection);
chatManager = org.jivesoftware.smack.chat.ChatManager.getInstanceFor(connection); ChatManager chatManager = ChatManager.getInstanceFor(connection);
chatManager.addOutgoingMessageInterceptor(outgoingInterceptor, filter); chatManager.addOutgoingListener(outgoingInterceptor);
chatManager.addChatListener(incomingInterceptor); connection.addAsyncStanzaListener(new IncomingMessageInterceptor(),
new AndFilter(INCOMING_MESSAGE_FILTER, new StanzaExtensionFilter(NAMESPACE)));
ServiceDiscoveryManager.getInstanceFor(connection).addFeature(NAMESPACE); ServiceDiscoveryManager.getInstanceFor(connection).addFeature(NAMESPACE);
INSTANCES.put(connection, this); INSTANCES.put(connection, this);
} }
/**
* Register a ChatStateListener. That listener will be informed about changed chat states.
*
* @param listener chatStateListener
* @return true, if the listener was not registered before
*/
public boolean addChatStateListener(ChatStateListener listener) {
return chatStateListeners.add(listener);
}
/**
* Unregister a ChatStateListener.
*
* @param listener chatStateListener
* @return true, if the listener was registered before
*/
public boolean removeChatStateListener(ChatStateListener listener) {
return chatStateListeners.remove(listener);
}
/** /**
* Sets the current state of the provided chat. This method will send an empty bodied Message * Sets the current state of the provided chat. This method will send an empty bodied Message
@ -101,10 +148,10 @@ public final class ChatStateManager extends Manager {
* *
* @param newState the new state of the chat * @param newState the new state of the chat
* @param chat the chat. * @param chat the chat.
* @throws NotConnectedException * @throws NotConnectedException
* @throws InterruptedException * @throws InterruptedException
*/ */
public void setCurrentState(ChatState newState, org.jivesoftware.smack.chat.Chat chat) throws NotConnectedException, InterruptedException { public void setCurrentState(ChatState newState, Chat chat) throws NotConnectedException, InterruptedException {
if (chat == null || newState == null) { if (chat == null || newState == null) {
throw new IllegalArgumentException("Arguments cannot be null."); throw new IllegalArgumentException("Arguments cannot be null.");
} }
@ -115,7 +162,7 @@ public final class ChatStateManager extends Manager {
ChatStateExtension extension = new ChatStateExtension(newState); ChatStateExtension extension = new ChatStateExtension(newState);
message.addExtension(extension); message.addExtension(extension);
chat.sendMessage(message); chat.send(message);
} }
@ -135,7 +182,7 @@ public final class ChatStateManager extends Manager {
return connection().hashCode(); return connection().hashCode();
} }
private synchronized boolean updateChatState(org.jivesoftware.smack.chat.Chat chat, ChatState newState) { private synchronized boolean updateChatState(Chat chat, ChatState newState) {
ChatState lastChatState = chatStates.get(chat); ChatState lastChatState = chatStates.get(chat);
if (lastChatState != newState) { if (lastChatState != newState) {
chatStates.put(chat, newState); chatStates.put(chat, newState);
@ -144,38 +191,44 @@ public final class ChatStateManager extends Manager {
return false; return false;
} }
private static void fireNewChatState(org.jivesoftware.smack.chat.Chat chat, ChatState state, Message message) { private void fireNewChatState(Chat chat, ChatState state, Message message) {
for (ChatMessageListener listener : chat.getListeners()) { for (ChatStateListener listener : chatStateListeners) {
if (listener instanceof ChatStateListener) { listener.stateChanged(chat, state, message);
((ChatStateListener) listener).stateChanged(chat, state, message);
}
} }
} }
private class OutgoingMessageInterceptor implements MessageListener { private class OutgoingMessageInterceptor implements OutgoingChatMessageListener {
@Override @Override
public void processMessage(Message message) { public void newOutgoingMessage(EntityBareJid to, Message message, Chat chat) {
org.jivesoftware.smack.chat.Chat chat = chatManager.getThreadChat(message.getThread());
if (chat == null) { if (chat == null) {
return; return;
} }
// if message already has a chatStateExtension, then do nothing,
if (!filter.accept(message)) {
return;
}
// otherwise add a chatState extension if necessary.
if (updateChatState(chat, ChatState.active)) { if (updateChatState(chat, ChatState.active)) {
message.addExtension(new ChatStateExtension(ChatState.active)); message.addExtension(new ChatStateExtension(ChatState.active));
} }
} }
} }
private static class IncomingMessageInterceptor implements ChatManagerListener, ChatMessageListener { private class IncomingMessageInterceptor implements StanzaListener {
@Override @Override
public void chatCreated(final org.jivesoftware.smack.chat.Chat chat, boolean createdLocally) { public void processStanza(Stanza packet) {
chat.addMessageListener(this); Message message = (Message) packet;
}
@Override EntityFullJid fullFrom = message.getFrom().asEntityFullJidIfPossible();
public void processMessage(org.jivesoftware.smack.chat.Chat chat, Message message) { EntityBareJid bareFrom = fullFrom.asEntityBareJid();
Chat chat = ChatManager.getInstanceFor(connection()).chatWith(bareFrom);
ExtensionElement extension = message.getExtension(NAMESPACE); ExtensionElement extension = message.getExtension(NAMESPACE);
if (extension == null) { if (extension == null) {
return; return;
} }

View File

@ -1,6 +1,6 @@
/** /**
* *
* Copyright 2015 Florian Schmaus * Copyright 2015 - 2018 Florian Schmaus, Paul Schaub
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -16,6 +16,6 @@
*/ */
/** /**
* Smacks implementation of XEP-0085: Chat State Notifications. * Classes for Chat States (<a href="http://www.xmpp.org/extensions/xep-0085.html">XEP-0085</a>).
*/ */
package org.jivesoftware.smackx.chatstates; package org.jivesoftware.smackx.chatstates;

View File

@ -0,0 +1,91 @@
/**
*
* Copyright 2018 Paul Schaub
*
* 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.jivesoftware.smackx.chatstate;
import org.jivesoftware.smack.chat2.Chat;
import org.jivesoftware.smack.chat2.ChatManager;
import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smackx.chatstates.ChatState;
import org.jivesoftware.smackx.chatstates.ChatStateListener;
import org.jivesoftware.smackx.chatstates.ChatStateManager;
import org.igniterealtime.smack.inttest.AbstractSmackIntegrationTest;
import org.igniterealtime.smack.inttest.SmackIntegrationTest;
import org.igniterealtime.smack.inttest.SmackIntegrationTestEnvironment;
import org.igniterealtime.smack.inttest.util.SimpleResultSyncPoint;
import org.junit.After;
public class ChatStateIntegrationTest extends AbstractSmackIntegrationTest {
// Listener for composing chat state
private final SimpleResultSyncPoint composingSyncPoint = new SimpleResultSyncPoint();
private final ChatStateListener composingListener = new ChatStateListener() {
@Override
public void stateChanged(Chat chat, ChatState state, Message message) {
if (state.equals(ChatState.composing)) {
composingSyncPoint.signal();
}
}
};
// Listener for active chat state
private final SimpleResultSyncPoint activeSyncPoint = new SimpleResultSyncPoint();
private final ChatStateListener activeListener = new ChatStateListener() {
@Override
public void stateChanged(Chat chat, ChatState state, Message message) {
if (state.equals(ChatState.active)) {
activeSyncPoint.signal();
}
}
};
public ChatStateIntegrationTest(SmackIntegrationTestEnvironment environment) {
super(environment);
}
@SmackIntegrationTest
public void testChatStateListeners() throws Exception {
ChatStateManager manOne = ChatStateManager.getInstance(conOne);
ChatStateManager manTwo = ChatStateManager.getInstance(conTwo);
// Add chatState listeners.
manTwo.addChatStateListener(composingListener);
manTwo.addChatStateListener(activeListener);
Chat chatOne = ChatManager.getInstanceFor(conOne)
.chatWith(conTwo.getUser().asEntityBareJid());
// Test, if setCurrentState works and the chatState arrives
manOne.setCurrentState(ChatState.composing, chatOne);
composingSyncPoint.waitForResult(timeout);
// Test, if the OutgoingMessageInterceptor successfully adds a chatStateExtension of "active" to
// an outgoing chat message and if it arrives at the other side.
Chat chat = ChatManager.getInstanceFor(conOne)
.chatWith(conTwo.getUser().asEntityBareJid());
chat.send("Hi!");
activeSyncPoint.waitForResult(timeout);
}
@After
public void cleanup() {
ChatStateManager manTwo = ChatStateManager.getInstance(conTwo);
manTwo.removeChatStateListener(composingListener);
manTwo.removeChatStateListener(activeListener);
}
}

View File

@ -0,0 +1,21 @@
/**
*
* Copyright 2018 Paul Schaub
*
* 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.
*/
/**
* TODO describe me.
*/
package org.jivesoftware.smackx.chatstate;