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 21package org.jivesoftware.smack; 22 23import java.util.Collection; 24import java.util.Collections; 25import java.util.Map; 26import java.util.Set; 27import java.util.WeakHashMap; 28import java.util.concurrent.CopyOnWriteArraySet; 29 30import org.jivesoftware.smack.filter.AndFilter; 31import org.jivesoftware.smack.filter.FromContainsFilter; 32import org.jivesoftware.smack.filter.PacketFilter; 33import org.jivesoftware.smack.filter.ThreadFilter; 34import org.jivesoftware.smack.packet.Message; 35import org.jivesoftware.smack.packet.Packet; 36import org.jivesoftware.smack.util.StringUtils; 37import org.jivesoftware.smack.util.collections.ReferenceMap; 38 39import java.util.*; 40import java.util.concurrent.CopyOnWriteArraySet; 41 42/** 43 * The chat manager keeps track of references to all current chats. It will not hold any references 44 * in memory on its own so it is neccesary to keep a reference to the chat object itself. To be 45 * made aware of new chats, register a listener by calling {@link #addChatListener(ChatManagerListener)}. 46 * 47 * @author Alexander Wenckus 48 */ 49public class ChatManager { 50 51 /** 52 * Returns the next unique id. Each id made up of a short alphanumeric 53 * prefix along with a unique numeric value. 54 * 55 * @return the next id. 56 */ 57 private static synchronized String nextID() { 58 return prefix + Long.toString(id++); 59 } 60 61 /** 62 * A prefix helps to make sure that ID's are unique across mutliple instances. 63 */ 64 private static String prefix = StringUtils.randomString(5); 65 66 /** 67 * Keeps track of the current increment, which is appended to the prefix to 68 * forum a unique ID. 69 */ 70 private static long id = 0; 71 72 /** 73 * Maps thread ID to chat. 74 */ 75 private Map<String, Chat> threadChats = Collections.synchronizedMap(new ReferenceMap<String, Chat>(ReferenceMap.HARD, 76 ReferenceMap.WEAK)); 77 78 /** 79 * Maps jids to chats 80 */ 81 private Map<String, Chat> jidChats = Collections.synchronizedMap(new ReferenceMap<String, Chat>(ReferenceMap.HARD, 82 ReferenceMap.WEAK)); 83 84 /** 85 * Maps base jids to chats 86 */ 87 private Map<String, Chat> baseJidChats = Collections.synchronizedMap(new ReferenceMap<String, Chat>(ReferenceMap.HARD, 88 ReferenceMap.WEAK)); 89 90 private Set<ChatManagerListener> chatManagerListeners 91 = new CopyOnWriteArraySet<ChatManagerListener>(); 92 93 private Map<PacketInterceptor, PacketFilter> interceptors 94 = new WeakHashMap<PacketInterceptor, PacketFilter>(); 95 96 private Connection connection; 97 98 ChatManager(Connection connection) { 99 this.connection = connection; 100 101 PacketFilter filter = new PacketFilter() { 102 public boolean accept(Packet packet) { 103 if (!(packet instanceof Message)) { 104 return false; 105 } 106 Message.Type messageType = ((Message) packet).getType(); 107 return messageType != Message.Type.groupchat && 108 messageType != Message.Type.headline; 109 } 110 }; 111 // Add a listener for all message packets so that we can deliver errant 112 // messages to the best Chat instance available. 113 connection.addPacketListener(new PacketListener() { 114 public void processPacket(Packet packet) { 115 Message message = (Message) packet; 116 Chat chat; 117 if (message.getThread() == null) { 118 chat = getUserChat(message.getFrom()); 119 } 120 else { 121 chat = getThreadChat(message.getThread()); 122 if (chat == null) { 123 // Try to locate the chat based on the sender of the message 124 chat = getUserChat(message.getFrom()); 125 } 126 } 127 128 if(chat == null) { 129 chat = createChat(message); 130 } 131 deliverMessage(chat, message); 132 } 133 }, filter); 134 } 135 136 /** 137 * Creates a new chat and returns it. 138 * 139 * @param userJID the user this chat is with. 140 * @param listener the listener which will listen for new messages from this chat. 141 * @return the created chat. 142 */ 143 public Chat createChat(String userJID, MessageListener listener) { 144 String threadID; 145 do { 146 threadID = nextID(); 147 } while (threadChats.get(threadID) != null); 148 149 return createChat(userJID, threadID, listener); 150 } 151 152 /** 153 * Creates a new chat using the specified thread ID, then returns it. 154 * 155 * @param userJID the jid of the user this chat is with 156 * @param thread the thread of the created chat. 157 * @param listener the listener to add to the chat 158 * @return the created chat. 159 */ 160 public Chat createChat(String userJID, String thread, MessageListener listener) { 161 if(thread == null) { 162 thread = nextID(); 163 } 164 Chat chat = threadChats.get(thread); 165 if(chat != null) { 166 throw new IllegalArgumentException("ThreadID is already used"); 167 } 168 chat = createChat(userJID, thread, true); 169 chat.addMessageListener(listener); 170 return chat; 171 } 172 173 private Chat createChat(String userJID, String threadID, boolean createdLocally) { 174 Chat chat = new Chat(this, userJID, threadID); 175 threadChats.put(threadID, chat); 176 jidChats.put(userJID, chat); 177 baseJidChats.put(StringUtils.parseBareAddress(userJID), chat); 178 179 for(ChatManagerListener listener : chatManagerListeners) { 180 listener.chatCreated(chat, createdLocally); 181 } 182 183 return chat; 184 } 185 186 private Chat createChat(Message message) { 187 String threadID = message.getThread(); 188 if(threadID == null) { 189 threadID = nextID(); 190 } 191 String userJID = message.getFrom(); 192 193 return createChat(userJID, threadID, false); 194 } 195 196 /** 197 * Try to get a matching chat for the given user JID. Try the full 198 * JID map first, the try to match on the base JID if no match is 199 * found. 200 * 201 * @param userJID 202 * @return 203 */ 204 private Chat getUserChat(String userJID) { 205 Chat match = jidChats.get(userJID); 206 207 if (match == null) { 208 match = baseJidChats.get(StringUtils.parseBareAddress(userJID)); 209 } 210 return match; 211 } 212 213 public Chat getThreadChat(String thread) { 214 return threadChats.get(thread); 215 } 216 217 /** 218 * Register a new listener with the ChatManager to recieve events related to chats. 219 * 220 * @param listener the listener. 221 */ 222 public void addChatListener(ChatManagerListener listener) { 223 chatManagerListeners.add(listener); 224 } 225 226 /** 227 * Removes a listener, it will no longer be notified of new events related to chats. 228 * 229 * @param listener the listener that is being removed 230 */ 231 public void removeChatListener(ChatManagerListener listener) { 232 chatManagerListeners.remove(listener); 233 } 234 235 /** 236 * Returns an unmodifiable collection of all chat listeners currently registered with this 237 * manager. 238 * 239 * @return an unmodifiable collection of all chat listeners currently registered with this 240 * manager. 241 */ 242 public Collection<ChatManagerListener> getChatListeners() { 243 return Collections.unmodifiableCollection(chatManagerListeners); 244 } 245 246 private void deliverMessage(Chat chat, Message message) { 247 // Here we will run any interceptors 248 chat.deliver(message); 249 } 250 251 void sendMessage(Chat chat, Message message) { 252 for(Map.Entry<PacketInterceptor, PacketFilter> interceptor : interceptors.entrySet()) { 253 PacketFilter filter = interceptor.getValue(); 254 if(filter != null && filter.accept(message)) { 255 interceptor.getKey().interceptPacket(message); 256 } 257 } 258 // Ensure that messages being sent have a proper FROM value 259 if (message.getFrom() == null) { 260 message.setFrom(connection.getUser()); 261 } 262 connection.sendPacket(message); 263 } 264 265 PacketCollector createPacketCollector(Chat chat) { 266 return connection.createPacketCollector(new AndFilter(new ThreadFilter(chat.getThreadID()), 267 new FromContainsFilter(chat.getParticipant()))); 268 } 269 270 /** 271 * Adds an interceptor which intercepts any messages sent through chats. 272 * 273 * @param packetInterceptor the interceptor. 274 */ 275 public void addOutgoingMessageInterceptor(PacketInterceptor packetInterceptor) { 276 addOutgoingMessageInterceptor(packetInterceptor, null); 277 } 278 279 public void addOutgoingMessageInterceptor(PacketInterceptor packetInterceptor, PacketFilter filter) { 280 if (packetInterceptor != null) { 281 interceptors.put(packetInterceptor, filter); 282 } 283 } 284} 285