blob: 70c95ee2e823e74dfccaec9f4fa7b954e8b4888e [file] [log] [blame]
Shuyi Chend7955ce2013-05-22 14:51:55 -07001/**
2 * $Revision$
3 * $Date$
4 *
5 * Copyright 2003-2007 Jive Software.
6 *
7 * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
8 * you may not use this file except in compliance with the License.
9 * You may obtain a copy of the License at
10 *
11 * http://www.apache.org/licenses/LICENSE-2.0
12 *
13 * Unless required by applicable law or agreed to in writing, software
14 * distributed under the License is distributed on an "AS IS" BASIS,
15 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 * See the License for the specific language governing permissions and
17 * limitations under the License.
18 */
19
20package org.jivesoftware.smackx.workgroup.agent;
21
22import org.jivesoftware.smackx.workgroup.packet.AgentStatus;
23import org.jivesoftware.smackx.workgroup.packet.AgentStatusRequest;
24import org.jivesoftware.smack.PacketListener;
25import org.jivesoftware.smack.Connection;
26import org.jivesoftware.smack.filter.PacketFilter;
27import org.jivesoftware.smack.filter.PacketTypeFilter;
28import org.jivesoftware.smack.packet.Packet;
29import org.jivesoftware.smack.packet.Presence;
30import org.jivesoftware.smack.util.StringUtils;
31
32import java.util.ArrayList;
33import java.util.Collections;
34import java.util.HashMap;
35import java.util.HashSet;
36import java.util.Iterator;
37import java.util.List;
38import java.util.Map;
39import java.util.Set;
40
41/**
42 * Manges information about the agents in a workgroup and their presence.
43 *
44 * @author Matt Tucker
45 * @see AgentSession#getAgentRoster()
46 */
47public class AgentRoster {
48
49 private static final int EVENT_AGENT_ADDED = 0;
50 private static final int EVENT_AGENT_REMOVED = 1;
51 private static final int EVENT_PRESENCE_CHANGED = 2;
52
53 private Connection connection;
54 private String workgroupJID;
55 private List<String> entries;
56 private List<AgentRosterListener> listeners;
57 private Map<String, Map<String, Presence>> presenceMap;
58 // The roster is marked as initialized when at least a single roster packet
59 // has been recieved and processed.
60 boolean rosterInitialized = false;
61
62 /**
63 * Constructs a new AgentRoster.
64 *
65 * @param connection an XMPP connection.
66 */
67 AgentRoster(Connection connection, String workgroupJID) {
68 this.connection = connection;
69 this.workgroupJID = workgroupJID;
70 entries = new ArrayList<String>();
71 listeners = new ArrayList<AgentRosterListener>();
72 presenceMap = new HashMap<String, Map<String, Presence>>();
73 // Listen for any roster packets.
74 PacketFilter rosterFilter = new PacketTypeFilter(AgentStatusRequest.class);
75 connection.addPacketListener(new AgentStatusListener(), rosterFilter);
76 // Listen for any presence packets.
77 connection.addPacketListener(new PresencePacketListener(),
78 new PacketTypeFilter(Presence.class));
79
80 // Send request for roster.
81 AgentStatusRequest request = new AgentStatusRequest();
82 request.setTo(workgroupJID);
83 connection.sendPacket(request);
84 }
85
86 /**
87 * Reloads the entire roster from the server. This is an asynchronous operation,
88 * which means the method will return immediately, and the roster will be
89 * reloaded at a later point when the server responds to the reload request.
90 */
91 public void reload() {
92 AgentStatusRequest request = new AgentStatusRequest();
93 request.setTo(workgroupJID);
94 connection.sendPacket(request);
95 }
96
97 /**
98 * Adds a listener to this roster. The listener will be fired anytime one or more
99 * changes to the roster are pushed from the server.
100 *
101 * @param listener an agent roster listener.
102 */
103 public void addListener(AgentRosterListener listener) {
104 synchronized (listeners) {
105 if (!listeners.contains(listener)) {
106 listeners.add(listener);
107
108 // Fire events for the existing entries and presences in the roster
109 for (Iterator<String> it = getAgents().iterator(); it.hasNext();) {
110 String jid = it.next();
111 // Check again in case the agent is no longer in the roster (highly unlikely
112 // but possible)
113 if (entries.contains(jid)) {
114 // Fire the agent added event
115 listener.agentAdded(jid);
116 Map<String,Presence> userPresences = presenceMap.get(jid);
117 if (userPresences != null) {
118 Iterator<Presence> presences = userPresences.values().iterator();
119 while (presences.hasNext()) {
120 // Fire the presence changed event
121 listener.presenceChanged(presences.next());
122 }
123 }
124 }
125 }
126 }
127 }
128 }
129
130 /**
131 * Removes a listener from this roster. The listener will be fired anytime one or more
132 * changes to the roster are pushed from the server.
133 *
134 * @param listener a roster listener.
135 */
136 public void removeListener(AgentRosterListener listener) {
137 synchronized (listeners) {
138 listeners.remove(listener);
139 }
140 }
141
142 /**
143 * Returns a count of all agents in the workgroup.
144 *
145 * @return the number of agents in the workgroup.
146 */
147 public int getAgentCount() {
148 return entries.size();
149 }
150
151 /**
152 * Returns all agents (String JID values) in the workgroup.
153 *
154 * @return all entries in the roster.
155 */
156 public Set<String> getAgents() {
157 Set<String> agents = new HashSet<String>();
158 synchronized (entries) {
159 for (Iterator<String> i = entries.iterator(); i.hasNext();) {
160 agents.add(i.next());
161 }
162 }
163 return Collections.unmodifiableSet(agents);
164 }
165
166 /**
167 * Returns true if the specified XMPP address is an agent in the workgroup.
168 *
169 * @param jid the XMPP address of the agent (eg "jsmith@example.com"). The
170 * address can be in any valid format (e.g. "domain/resource", "user@domain"
171 * or "user@domain/resource").
172 * @return true if the XMPP address is an agent in the workgroup.
173 */
174 public boolean contains(String jid) {
175 if (jid == null) {
176 return false;
177 }
178 synchronized (entries) {
179 for (Iterator<String> i = entries.iterator(); i.hasNext();) {
180 String entry = i.next();
181 if (entry.toLowerCase().equals(jid.toLowerCase())) {
182 return true;
183 }
184 }
185 }
186 return false;
187 }
188
189 /**
190 * Returns the presence info for a particular agent, or <tt>null</tt> if the agent
191 * is unavailable (offline) or if no presence information is available.<p>
192 *
193 * @param user a fully qualified xmpp JID. The address could be in any valid format (e.g.
194 * "domain/resource", "user@domain" or "user@domain/resource").
195 * @return the agent's current presence, or <tt>null</tt> if the agent is unavailable
196 * or if no presence information is available..
197 */
198 public Presence getPresence(String user) {
199 String key = getPresenceMapKey(user);
200 Map<String, Presence> userPresences = presenceMap.get(key);
201 if (userPresences == null) {
202 Presence presence = new Presence(Presence.Type.unavailable);
203 presence.setFrom(user);
204 return presence;
205 }
206 else {
207 // Find the resource with the highest priority
208 // Might be changed to use the resource with the highest availability instead.
209 Iterator<String> it = userPresences.keySet().iterator();
210 Presence p;
211 Presence presence = null;
212
213 while (it.hasNext()) {
214 p = (Presence)userPresences.get(it.next());
215 if (presence == null){
216 presence = p;
217 }
218 else {
219 if (p.getPriority() > presence.getPriority()) {
220 presence = p;
221 }
222 }
223 }
224 if (presence == null) {
225 presence = new Presence(Presence.Type.unavailable);
226 presence.setFrom(user);
227 return presence;
228 }
229 else {
230 return presence;
231 }
232 }
233 }
234
235 /**
236 * Returns the key to use in the presenceMap for a fully qualified xmpp ID. The roster
237 * can contain any valid address format such us "domain/resource", "user@domain" or
238 * "user@domain/resource". If the roster contains an entry associated with the fully qualified
239 * xmpp ID then use the fully qualified xmpp ID as the key in presenceMap, otherwise use the
240 * bare address. Note: When the key in presenceMap is a fully qualified xmpp ID, the
241 * userPresences is useless since it will always contain one entry for the user.
242 *
243 * @param user the fully qualified xmpp ID, e.g. jdoe@example.com/Work.
244 * @return the key to use in the presenceMap for the fully qualified xmpp ID.
245 */
246 private String getPresenceMapKey(String user) {
247 String key = user;
248 if (!contains(user)) {
249 key = StringUtils.parseBareAddress(user).toLowerCase();
250 }
251 return key;
252 }
253
254 /**
255 * Fires event to listeners.
256 */
257 private void fireEvent(int eventType, Object eventObject) {
258 AgentRosterListener[] listeners = null;
259 synchronized (this.listeners) {
260 listeners = new AgentRosterListener[this.listeners.size()];
261 this.listeners.toArray(listeners);
262 }
263 for (int i = 0; i < listeners.length; i++) {
264 switch (eventType) {
265 case EVENT_AGENT_ADDED:
266 listeners[i].agentAdded((String)eventObject);
267 break;
268 case EVENT_AGENT_REMOVED:
269 listeners[i].agentRemoved((String)eventObject);
270 break;
271 case EVENT_PRESENCE_CHANGED:
272 listeners[i].presenceChanged((Presence)eventObject);
273 break;
274 }
275 }
276 }
277
278 /**
279 * Listens for all presence packets and processes them.
280 */
281 private class PresencePacketListener implements PacketListener {
282 public void processPacket(Packet packet) {
283 Presence presence = (Presence)packet;
284 String from = presence.getFrom();
285 if (from == null) {
286 // TODO Check if we need to ignore these presences or this is a server bug?
287 System.out.println("Presence with no FROM: " + presence.toXML());
288 return;
289 }
290 String key = getPresenceMapKey(from);
291
292 // If an "available" packet, add it to the presence map. Each presence map will hold
293 // for a particular user a map with the presence packets saved for each resource.
294 if (presence.getType() == Presence.Type.available) {
295 // Ignore the presence packet unless it has an agent status extension.
296 AgentStatus agentStatus = (AgentStatus)presence.getExtension(
297 AgentStatus.ELEMENT_NAME, AgentStatus.NAMESPACE);
298 if (agentStatus == null) {
299 return;
300 }
301 // Ensure that this presence is coming from an Agent of the same workgroup
302 // of this Agent
303 else if (!workgroupJID.equals(agentStatus.getWorkgroupJID())) {
304 return;
305 }
306 Map<String, Presence> userPresences;
307 // Get the user presence map
308 if (presenceMap.get(key) == null) {
309 userPresences = new HashMap<String, Presence>();
310 presenceMap.put(key, userPresences);
311 }
312 else {
313 userPresences = presenceMap.get(key);
314 }
315 // Add the new presence, using the resources as a key.
316 synchronized (userPresences) {
317 userPresences.put(StringUtils.parseResource(from), presence);
318 }
319 // Fire an event.
320 synchronized (entries) {
321 for (Iterator<String> i = entries.iterator(); i.hasNext();) {
322 String entry = i.next();
323 if (entry.toLowerCase().equals(StringUtils.parseBareAddress(key).toLowerCase())) {
324 fireEvent(EVENT_PRESENCE_CHANGED, packet);
325 }
326 }
327 }
328 }
329 // If an "unavailable" packet, remove any entries in the presence map.
330 else if (presence.getType() == Presence.Type.unavailable) {
331 if (presenceMap.get(key) != null) {
332 Map<String,Presence> userPresences = presenceMap.get(key);
333 synchronized (userPresences) {
334 userPresences.remove(StringUtils.parseResource(from));
335 }
336 if (userPresences.isEmpty()) {
337 presenceMap.remove(key);
338 }
339 }
340 // Fire an event.
341 synchronized (entries) {
342 for (Iterator<String> i = entries.iterator(); i.hasNext();) {
343 String entry = (String)i.next();
344 if (entry.toLowerCase().equals(StringUtils.parseBareAddress(key).toLowerCase())) {
345 fireEvent(EVENT_PRESENCE_CHANGED, packet);
346 }
347 }
348 }
349 }
350 }
351 }
352
353 /**
354 * Listens for all roster packets and processes them.
355 */
356 private class AgentStatusListener implements PacketListener {
357
358 public void processPacket(Packet packet) {
359 if (packet instanceof AgentStatusRequest) {
360 AgentStatusRequest statusRequest = (AgentStatusRequest)packet;
361 for (Iterator<AgentStatusRequest.Item> i = statusRequest.getAgents().iterator(); i.hasNext();) {
362 AgentStatusRequest.Item item = i.next();
363 String agentJID = item.getJID();
364 if ("remove".equals(item.getType())) {
365
366 // Removing the user from the roster, so remove any presence information
367 // about them.
368 String key = StringUtils.parseName(StringUtils.parseName(agentJID) + "@" +
369 StringUtils.parseServer(agentJID));
370 presenceMap.remove(key);
371 // Fire event for roster listeners.
372 fireEvent(EVENT_AGENT_REMOVED, agentJID);
373 }
374 else {
375 entries.add(agentJID);
376 // Fire event for roster listeners.
377 fireEvent(EVENT_AGENT_ADDED, agentJID);
378 }
379 }
380
381 // Mark the roster as initialized.
382 rosterInitialized = true;
383 }
384 }
385 }
386}