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 */ getInstance(final Connection connection)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 ChatStateManager(Connection connection)92 private ChatStateManager(Connection connection) { 93 this.connection = connection; 94 } 95 init()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 */ setCurrentState(ChatState newState, Chat chat)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 equals(Object o)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 hashCode()141 public int hashCode() { 142 return connection.hashCode(); 143 } 144 updateChatState(Chat chat, ChatState newState)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 fireNewChatState(Chat chat, ChatState state)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 interceptPacket(Packet packet)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 chatCreated(final Chat chat, boolean createdLocally)178 public void chatCreated(final Chat chat, boolean createdLocally) { 179 chat.addMessageListener(this); 180 } 181 processMessage(Chat chat, Message message)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