Home | History | Annotate | Download | only in smackx
      1 /**
      2  * $RCSfile$
      3  * $Revision: 2407 $
      4  * $Date: 2004-11-02 15:37:00 -0800 (Tue, 02 Nov 2004) $
      5  *
      6  * Copyright 2003-2007 Jive Software.
      7  *
      8  * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
      9  * you may not use this file except in compliance with the License.
     10  * You may obtain a copy of the License at
     11  *
     12  *     http://www.apache.org/licenses/LICENSE-2.0
     13  *
     14  * Unless required by applicable law or agreed to in writing, software
     15  * distributed under the License is distributed on an "AS IS" BASIS,
     16  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     17  * See the License for the specific language governing permissions and
     18  * limitations under the License.
     19  */
     20 
     21 package org.jivesoftware.smackx;
     22 
     23 import org.jivesoftware.smack.*;
     24 import org.jivesoftware.smack.util.collections.ReferenceMap;
     25 import org.jivesoftware.smack.filter.PacketFilter;
     26 import org.jivesoftware.smack.filter.NotFilter;
     27 import org.jivesoftware.smack.filter.PacketExtensionFilter;
     28 import org.jivesoftware.smack.packet.Message;
     29 import org.jivesoftware.smack.packet.Packet;
     30 import org.jivesoftware.smack.packet.PacketExtension;
     31 import org.jivesoftware.smackx.packet.ChatStateExtension;
     32 
     33 import java.util.Map;
     34 import java.util.WeakHashMap;
     35 
     36 /**
     37  * Handles chat state for all chats on a particular Connection. This class manages both the
     38  * packet extensions and the disco response neccesary for compliance with
     39  * <a href="http://www.xmpp.org/extensions/xep-0085.html">XEP-0085</a>.
     40  *
     41  * NOTE: {@link org.jivesoftware.smackx.ChatStateManager#getInstance(org.jivesoftware.smack.Connection)}
     42  * needs to be called in order for the listeners to be registered appropriately with the connection.
     43  * If this does not occur you will not receive the update notifications.
     44  *
     45  * @author Alexander Wenckus
     46  * @see org.jivesoftware.smackx.ChatState
     47  * @see org.jivesoftware.smackx.packet.ChatStateExtension
     48  */
     49 public class ChatStateManager {
     50 
     51     private static final Map<Connection, ChatStateManager> managers =
     52             new WeakHashMap<Connection, ChatStateManager>();
     53 
     54     private static final PacketFilter filter = new NotFilter(
     55                 new PacketExtensionFilter("http://jabber.org/protocol/chatstates"));
     56 
     57     /**
     58      * Returns the ChatStateManager related to the Connection and it will create one if it does
     59      * not yet exist.
     60      *
     61      * @param connection the connection to return the ChatStateManager
     62      * @return the ChatStateManager related the the connection.
     63      */
     64     public static ChatStateManager getInstance(final Connection connection) {
     65         if(connection == null) {
     66             return null;
     67         }
     68         synchronized (managers) {
     69             ChatStateManager manager = managers.get(connection);
     70             if (manager == null) {
     71                 manager = new ChatStateManager(connection);
     72                 manager.init();
     73                 managers.put(connection, manager);
     74             }
     75 
     76             return manager;
     77         }
     78     }
     79 
     80     private final Connection connection;
     81 
     82     private final OutgoingMessageInterceptor outgoingInterceptor = new OutgoingMessageInterceptor();
     83 
     84     private final IncomingMessageInterceptor incomingInterceptor = new IncomingMessageInterceptor();
     85 
     86     /**
     87      * Maps chat to last chat state.
     88      */
     89     private final Map<Chat, ChatState> chatStates =
     90             new ReferenceMap<Chat, ChatState>(ReferenceMap.WEAK, ReferenceMap.HARD);
     91 
     92     private ChatStateManager(Connection connection) {
     93         this.connection = connection;
     94     }
     95 
     96     private void init() {
     97         connection.getChatManager().addOutgoingMessageInterceptor(outgoingInterceptor,
     98                 filter);
     99         connection.getChatManager().addChatListener(incomingInterceptor);
    100 
    101         ServiceDiscoveryManager.getInstanceFor(connection)
    102                 .addFeature("http://jabber.org/protocol/chatstates");
    103     }
    104 
    105     /**
    106      * Sets the current state of the provided chat. This method will send an empty bodied Message
    107      * packet with the state attached as a {@link org.jivesoftware.smack.packet.PacketExtension}, if
    108      * and only if the new chat state is different than the last state.
    109      *
    110      * @param newState the new state of the chat
    111      * @param chat the chat.
    112      * @throws org.jivesoftware.smack.XMPPException
    113      *          when there is an error sending the message
    114      *          packet.
    115      */
    116     public void setCurrentState(ChatState newState, Chat chat) throws XMPPException {
    117         if(chat == null || newState == null) {
    118             throw new IllegalArgumentException("Arguments cannot be null.");
    119         }
    120         if(!updateChatState(chat, newState)) {
    121             return;
    122         }
    123         Message message = new Message();
    124         ChatStateExtension extension = new ChatStateExtension(newState);
    125         message.addExtension(extension);
    126 
    127         chat.sendMessage(message);
    128     }
    129 
    130 
    131     public boolean equals(Object o) {
    132         if (this == o) return true;
    133         if (o == null || getClass() != o.getClass()) return false;
    134 
    135         ChatStateManager that = (ChatStateManager) o;
    136 
    137         return connection.equals(that.connection);
    138 
    139     }
    140 
    141     public int hashCode() {
    142         return connection.hashCode();
    143     }
    144 
    145     private boolean updateChatState(Chat chat, ChatState newState) {
    146         ChatState lastChatState = chatStates.get(chat);
    147         if (lastChatState != newState) {
    148             chatStates.put(chat, newState);
    149             return true;
    150         }
    151         return false;
    152     }
    153 
    154     private void fireNewChatState(Chat chat, ChatState state) {
    155         for (MessageListener listener : chat.getListeners()) {
    156             if (listener instanceof ChatStateListener) {
    157                 ((ChatStateListener) listener).stateChanged(chat, state);
    158             }
    159         }
    160     }
    161 
    162     private class OutgoingMessageInterceptor implements PacketInterceptor {
    163 
    164         public void interceptPacket(Packet packet) {
    165             Message message = (Message) packet;
    166             Chat chat = connection.getChatManager().getThreadChat(message.getThread());
    167             if (chat == null) {
    168                 return;
    169             }
    170             if (updateChatState(chat, ChatState.active)) {
    171                 message.addExtension(new ChatStateExtension(ChatState.active));
    172             }
    173         }
    174     }
    175 
    176     private class IncomingMessageInterceptor implements ChatManagerListener, MessageListener {
    177 
    178         public void chatCreated(final Chat chat, boolean createdLocally) {
    179             chat.addMessageListener(this);
    180         }
    181 
    182         public void processMessage(Chat chat, Message message) {
    183             PacketExtension extension
    184                     = message.getExtension("http://jabber.org/protocol/chatstates");
    185             if (extension == null) {
    186                 return;
    187             }
    188 
    189             ChatState state;
    190             try {
    191                 state = ChatState.valueOf(extension.getElementName());
    192             }
    193             catch (Exception ex) {
    194                 return;
    195             }
    196 
    197             fireNewChatState(chat, state);
    198         }
    199     }
    200 }
    201