ChatStateManager.java

  1. /**
  2.  *
  3.  * Copyright 2003-2007 Jive Software.
  4.  *
  5.  * Licensed under the Apache License, Version 2.0 (the "License");
  6.  * you may not use this file except in compliance with the License.
  7.  * You may obtain a copy of the License at
  8.  *
  9.  *     http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */

  17. package org.jivesoftware.smackx.chatstates;

  18. import java.util.Map;
  19. import java.util.WeakHashMap;

  20. import org.jivesoftware.smack.MessageListener;
  21. import org.jivesoftware.smack.SmackException.NotConnectedException;
  22. import org.jivesoftware.smack.XMPPConnection;
  23. import org.jivesoftware.smack.Manager;
  24. import org.jivesoftware.smack.chat.Chat;
  25. import org.jivesoftware.smack.chat.ChatManager;
  26. import org.jivesoftware.smack.chat.ChatManagerListener;
  27. import org.jivesoftware.smack.chat.ChatMessageListener;
  28. import org.jivesoftware.smack.filter.NotFilter;
  29. import org.jivesoftware.smack.filter.StanzaExtensionFilter;
  30. import org.jivesoftware.smack.filter.StanzaFilter;
  31. import org.jivesoftware.smack.packet.Message;
  32. import org.jivesoftware.smack.packet.ExtensionElement;
  33. import org.jivesoftware.smackx.chatstates.packet.ChatStateExtension;
  34. import org.jivesoftware.smackx.disco.ServiceDiscoveryManager;

  35. /**
  36.  * Handles chat state for all chats on a particular XMPPConnection. This class manages both the
  37.  * packet extensions and the disco response necessary for compliance with
  38.  * <a href="http://www.xmpp.org/extensions/xep-0085.html">XEP-0085</a>.
  39.  *
  40.  * NOTE: {@link org.jivesoftware.smackx.chatstates.ChatStateManager#getInstance(org.jivesoftware.smack.XMPPConnection)}
  41.  * needs to be called in order for the listeners to be registered appropriately with the connection.
  42.  * If this does not occur you will not receive the update notifications.
  43.  *
  44.  * @author Alexander Wenckus
  45.  * @see org.jivesoftware.smackx.chatstates.ChatState
  46.  * @see org.jivesoftware.smackx.chatstates.packet.ChatStateExtension
  47.  */
  48. public class ChatStateManager extends Manager {
  49.     public static final String NAMESPACE = "http://jabber.org/protocol/chatstates";

  50.     private static final Map<XMPPConnection, ChatStateManager> INSTANCES =
  51.             new WeakHashMap<XMPPConnection, ChatStateManager>();

  52.     private static final StanzaFilter filter = new NotFilter(new StanzaExtensionFilter(NAMESPACE));

  53.     /**
  54.      * Returns the ChatStateManager related to the XMPPConnection and it will create one if it does
  55.      * not yet exist.
  56.      *
  57.      * @param connection the connection to return the ChatStateManager
  58.      * @return the ChatStateManager related the the connection.
  59.      */
  60.     public static synchronized ChatStateManager getInstance(final XMPPConnection connection) {
  61.             ChatStateManager manager = INSTANCES.get(connection);
  62.             if (manager == null) {
  63.                 manager = new ChatStateManager(connection);
  64.             }
  65.             return manager;
  66.     }

  67.     private final OutgoingMessageInterceptor outgoingInterceptor = new OutgoingMessageInterceptor();

  68.     private final IncomingMessageInterceptor incomingInterceptor = new IncomingMessageInterceptor();

  69.     /**
  70.      * Maps chat to last chat state.
  71.      */
  72.     private final Map<Chat, ChatState> chatStates = new WeakHashMap<Chat, ChatState>();

  73.     private final ChatManager chatManager;

  74.     private ChatStateManager(XMPPConnection connection) {
  75.         super(connection);
  76.         chatManager = ChatManager.getInstanceFor(connection);
  77.         chatManager.addOutgoingMessageInterceptor(outgoingInterceptor, filter);
  78.         chatManager.addChatListener(incomingInterceptor);

  79.         ServiceDiscoveryManager.getInstanceFor(connection).addFeature(NAMESPACE);
  80.         INSTANCES.put(connection, this);
  81.     }


  82.     /**
  83.      * Sets the current state of the provided chat. This method will send an empty bodied Message
  84.      * packet with the state attached as a {@link org.jivesoftware.smack.packet.ExtensionElement}, if
  85.      * and only if the new chat state is different than the last state.
  86.      *
  87.      * @param newState the new state of the chat
  88.      * @param chat the chat.
  89.      * @throws NotConnectedException
  90.      * @throws InterruptedException
  91.      */
  92.     public void setCurrentState(ChatState newState, Chat chat) throws NotConnectedException, InterruptedException {
  93.         if(chat == null || newState == null) {
  94.             throw new IllegalArgumentException("Arguments cannot be null.");
  95.         }
  96.         if(!updateChatState(chat, newState)) {
  97.             return;
  98.         }
  99.         Message message = new Message();
  100.         ChatStateExtension extension = new ChatStateExtension(newState);
  101.         message.addExtension(extension);

  102.         chat.sendMessage(message);
  103.     }


  104.     public boolean equals(Object o) {
  105.         if (this == o) return true;
  106.         if (o == null || getClass() != o.getClass()) return false;

  107.         ChatStateManager that = (ChatStateManager) o;

  108.         return connection().equals(that.connection());

  109.     }

  110.     public int hashCode() {
  111.         return connection().hashCode();
  112.     }

  113.     private synchronized boolean updateChatState(Chat chat, ChatState newState) {
  114.         ChatState lastChatState = chatStates.get(chat);
  115.         if (lastChatState != newState) {
  116.             chatStates.put(chat, newState);
  117.             return true;
  118.         }
  119.         return false;
  120.     }

  121.     private void fireNewChatState(Chat chat, ChatState state) {
  122.         for (ChatMessageListener listener : chat.getListeners()) {
  123.             if (listener instanceof ChatStateListener) {
  124.                 ((ChatStateListener) listener).stateChanged(chat, state);
  125.             }
  126.         }
  127.     }

  128.     private class OutgoingMessageInterceptor implements MessageListener {

  129.         @Override
  130.         public void processMessage(Message message) {
  131.             Chat chat = chatManager.getThreadChat(message.getThread());
  132.             if (chat == null) {
  133.                 return;
  134.             }
  135.             if (updateChatState(chat, ChatState.active)) {
  136.                 message.addExtension(new ChatStateExtension(ChatState.active));
  137.             }
  138.         }
  139.     }

  140.     private class IncomingMessageInterceptor implements ChatManagerListener, ChatMessageListener {

  141.         public void chatCreated(final Chat chat, boolean createdLocally) {
  142.             chat.addMessageListener(this);
  143.         }

  144.         public void processMessage(Chat chat, Message message) {
  145.             ExtensionElement extension = message.getExtension(NAMESPACE);
  146.             if (extension == null) {
  147.                 return;
  148.             }

  149.             ChatState state;
  150.             try {
  151.                 state = ChatState.valueOf(extension.getElementName());
  152.             }
  153.             catch (Exception ex) {
  154.                 return;
  155.             }

  156.             fireNewChatState(chat, state);
  157.         }
  158.     }
  159. }