• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /**
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 
20 package org.jivesoftware.smackx.workgroup.agent;
21 
22 import org.jivesoftware.smackx.workgroup.packet.AgentStatus;
23 import org.jivesoftware.smackx.workgroup.packet.AgentStatusRequest;
24 import org.jivesoftware.smack.PacketListener;
25 import org.jivesoftware.smack.Connection;
26 import org.jivesoftware.smack.filter.PacketFilter;
27 import org.jivesoftware.smack.filter.PacketTypeFilter;
28 import org.jivesoftware.smack.packet.Packet;
29 import org.jivesoftware.smack.packet.Presence;
30 import org.jivesoftware.smack.util.StringUtils;
31 
32 import java.util.ArrayList;
33 import java.util.Collections;
34 import java.util.HashMap;
35 import java.util.HashSet;
36 import java.util.Iterator;
37 import java.util.List;
38 import java.util.Map;
39 import 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  */
47 public 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      */
AgentRoster(Connection connection, String workgroupJID)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      */
reload()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      */
addListener(AgentRosterListener listener)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      */
removeListener(AgentRosterListener listener)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      */
getAgentCount()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      */
getAgents()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      */
contains(String jid)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      */
getPresence(String user)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      */
getPresenceMapKey(String user)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      */
fireEvent(int eventType, Object eventObject)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 {
processPacket(Packet packet)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 
processPacket(Packet packet)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 }