KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > jivesoftware > messenger > handler > PresenceUpdateHandler


1 /**
2  * $RCSfile: PresenceUpdateHandler.java,v $
3  * $Revision: 1.26 $
4  * $Date: 2005/07/26 05:15:53 $
5  *
6  * Copyright (C) 2004 Jive Software. All rights reserved.
7  *
8  * This software is published under the terms of the GNU Public License (GPL),
9  * a copy of which is included in this distribution.
10  */

11
12 package org.jivesoftware.messenger.handler;
13
14 import org.jivesoftware.messenger.*;
15 import org.jivesoftware.messenger.auth.UnauthorizedException;
16 import org.jivesoftware.messenger.container.BasicModule;
17 import org.jivesoftware.messenger.roster.Roster;
18 import org.jivesoftware.messenger.roster.RosterItem;
19 import org.jivesoftware.messenger.roster.RosterManager;
20 import org.jivesoftware.messenger.user.UserNotFoundException;
21 import org.jivesoftware.util.ConcurrentHashSet;
22 import org.jivesoftware.util.LocaleUtils;
23 import org.jivesoftware.util.Log;
24 import org.xmpp.packet.*;
25
26 import java.util.*;
27 import java.util.concurrent.ConcurrentHashMap JavaDoc;
28
29 /**
30  * Implements the presence protocol. Clients use this protocol to
31  * update presence and roster information.
32  * <p/>
33  * The handler must properly detect the presence type, update the user's roster,
34  * and inform presence subscribers of the session's updated presence
35  * status. Presence serves many purposes in Jabber so this handler will
36  * likely be the most complex of all handlers in the server.
37  * <p/>
38  * There are four basic types of presence updates:
39  * <ul>
40  * <li>Simple presence updates - addressed to the server (or to address), these updates
41  * are properly addressed by the server, and multicast to
42  * interested subscribers on the user's roster. An empty, missing,
43  * or "unavailable" type attribute indicates a simple update (there
44  * is no "available" type although it should be accepted by the server.
45  * <li>Directed presence updates - addressed to particular jabber entities,
46  * these presence updates are properly addressed and directly delivered
47  * to the entity without broadcast to roster subscribers. Any update type
48  * is possible except those reserved for subscription requests.
49  * <li>Subscription requests - these updates request presence subscription
50  * status changes. Such requests always affect the roster. The server must:
51  * <ul>
52  * <li>update the roster with the proper subscriber info
53  * <li>push the roster changes to the user
54  * <li>forward the update to the correct parties.
55  * </ul>
56  * The valid types include "subscribe", "subscribed", "unsubscribed",
57  * and "unsubscribe".
58  * <li>XMPPServer probes - Provides a mechanism for servers to query the presence
59  * status of users on another server. This allows users to immediately
60  * know the presence status of users when they come online rather than way
61  * for a presence update broadcast from the other server or tracking them
62  * as they are received. Requires S2S capabilities.
63  * </ul>
64  *
65  * @author Iain Shigeoka
66  */

67 public class PresenceUpdateHandler extends BasicModule implements ChannelHandler {
68
69     private Map JavaDoc<String JavaDoc, WeakHashMap<ChannelHandler, Set<String JavaDoc>>> directedPresences;
70
71     private RosterManager rosterManager;
72     private XMPPServer localServer;
73     private PresenceManager presenceManager;
74     private PacketDeliverer deliverer;
75     private OfflineMessageStore messageStore;
76     private SessionManager sessionManager;
77
78     public PresenceUpdateHandler() {
79         super("Presence update handler");
80         directedPresences = new ConcurrentHashMap JavaDoc<String JavaDoc, WeakHashMap<ChannelHandler, Set<String JavaDoc>>>();
81     }
82
83     public void process(Packet xmppPacket) throws UnauthorizedException, PacketException {
84         Presence presence = (Presence)xmppPacket;
85         try {
86             ClientSession session = sessionManager.getSession(presence.getFrom());
87             Presence.Type type = presence.getType();
88             // Available
89
if (type == null) {
90                 broadcastUpdate(presence.createCopy());
91                 if (session != null) {
92                     session.setPresence(presence);
93                     if (!session.isInitialized()) {
94                         initSession(session);
95                         session.setInitialized(true);
96                     }
97                 }
98                 // Notify the presence manager that the user is now available. The manager may
99
// remove the last presence status sent by the user when he went offline.
100
presenceManager.userAvailable(presence);
101             }
102             else if (Presence.Type.unavailable == type) {
103                 broadcastUpdate(presence.createCopy());
104                 broadcastUnavailableForDirectedPresences(presence);
105                 if (session != null) {
106                     session.setPresence(presence);
107                 }
108                 // Notify the presence manager that the user is now unavailable. The manager may
109
// save the last presence status sent by the user and keep track when the user
110
// went offline.
111
presenceManager.userUnavailable(presence);
112             }
113             else {
114                 presence = presence.createCopy();
115                 if (session != null) {
116                     presence.setFrom(new JID(null, session.getServerName(), null));
117                     presence.setTo(session.getAddress());
118                 }
119                 else {
120                     JID sender = presence.getFrom();
121                     presence.setFrom(presence.getTo());
122                     presence.setTo(sender);
123                 }
124                 presence.setError(PacketError.Condition.bad_request);
125                 deliverer.deliver(presence);
126             }
127
128         }
129         catch (Exception JavaDoc e) {
130             Log.error(LocaleUtils.getLocalizedString("admin.error"), e);
131         }
132     }
133
134     /**
135      * Handle presence updates that affect roster subscriptions.
136      *
137      * @param presence The presence presence to handle
138      */

139     public void process(Presence presence) throws PacketException {
140         try {
141             process((Packet)presence);
142         }
143         catch (UnauthorizedException e) {
144             try {
145                 Session session = sessionManager.getSession(presence.getFrom());
146                 presence = presence.createCopy();
147                 if (session != null) {
148                     presence.setFrom(new JID(null, session.getServerName(), null));
149                     presence.setTo(session.getAddress());
150                 }
151                 else {
152                     JID sender = presence.getFrom();
153                     presence.setFrom(presence.getTo());
154                     presence.setTo(sender);
155                 }
156                 presence.setError(PacketError.Condition.not_authorized);
157                 deliverer.deliver(presence);
158             }
159             catch (Exception JavaDoc err) {
160                 Log.error(LocaleUtils.getLocalizedString("admin.error"), err);
161             }
162         }
163     }
164
165     /**
166      * A session that has transitioned to available status must be initialized.
167      * This includes:
168      * <ul>
169      * <li>Sending all offline presence subscription requests</li>
170      * <li>Sending offline messages</li>
171      * </ul>
172      *
173      * @param session The session being updated
174      * @throws UserNotFoundException If the user being updated does not exist
175      */

176     private void initSession(ClientSession session) throws UserNotFoundException {
177
178         // Only user sessions need to be authenticated
179
if (session.getAddress().getNode() != null && !"".equals(session.getAddress().getNode())) {
180             String JavaDoc username = session.getAddress().getNode();
181             Roster roster = rosterManager.getRoster(username);
182             for (RosterItem item : roster.getRosterItems()) {
183                 if (item.getRecvStatus() == RosterItem.RECV_SUBSCRIBE) {
184                     session.process(createSubscribePresence(item.getJid(), true));
185                 }
186                 else if (item.getRecvStatus() == RosterItem.RECV_UNSUBSCRIBE) {
187                     session.process(createSubscribePresence(item.getJid(), false));
188                 }
189                 if (item.getSubStatus() == RosterItem.SUB_TO
190                         || item.getSubStatus() == RosterItem.SUB_BOTH) {
191                     presenceManager.probePresence(session.getAddress(), item.getJid());
192                 }
193             }
194             if (session.canFloodOfflineMessages()) {
195                 // deliver offline messages if any
196
Collection<OfflineMessage> messages = messageStore.getMessages(username, true);
197                 for (Message message : messages) {
198                     session.process(message);
199                 }
200             }
201         }
202     }
203
204     public Presence createSubscribePresence(JID senderAddress, boolean isSubscribe) {
205         Presence presence = new Presence();
206         presence.setFrom(senderAddress);
207         if (isSubscribe) {
208             presence.setType(Presence.Type.subscribe);
209         }
210         else {
211             presence.setType(Presence.Type.unsubscribe);
212         }
213         return presence;
214     }
215
216     /**
217      * Broadcast the given update to all subscribers. We need to:
218      * <ul>
219      * <li>Query the roster table for subscribers</li>
220      * <li>Iterate through the list and send the update to each subscriber</li>
221      * </ul>
222      * <p/>
223      * Is there a safe way to cache the query results while maintaining
224      * integrity with roster changes?
225      *
226      * @param update The update to broadcast
227      */

228     private void broadcastUpdate(Presence update) throws PacketException {
229         if (update.getFrom() == null) {
230             return;
231         }
232         if (localServer.isLocal(update.getFrom())) {
233             // Local updates can simply run through the roster of the local user
234
String JavaDoc name = update.getFrom().getNode();
235             try {
236                 if (name != null && !"".equals(name)) {
237                     name = name.toLowerCase();
238                     Roster roster = rosterManager.getRoster(name);
239                     roster.broadcastPresence(update);
240                 }
241             }
242             catch (UserNotFoundException e) {
243                 Log.warn("Presence being sent from unknown user " + name, e);
244             }
245             catch (PacketException e) {
246                 Log.error(LocaleUtils.getLocalizedString("admin.error"), e);
247             }
248         }
249         else {
250             // Foreign updates will do a reverse lookup of entries in rosters
251
// on the server
252
Log.warn("Presence requested from server "
253                     + localServer.getServerInfo().getName()
254                     + " by unknown user: " + update.getFrom());
255             /*
256             Connection con = null;
257             PreparedStatement pstmt = null;
258             try {
259
260                 pstmt = con.prepareStatement(GET_ROSTER_SUBS);
261                 pstmt.setString(1, update.getSender().toBareString().toLowerCase());
262                 ResultSet rs = pstmt.executeQuery();
263                 while (rs.next()){
264                     long userID = rs.getLong(1);
265                     try {
266                         User user = server.getUserManager().getUser(userID);
267
268                         update.setRecipient(user.getAddress());
269                         server.getSessionManager().userBroadcast(user.getUsername(),
270                                                                 update.getPacket());
271                     } catch (UserNotFoundException e) {
272                         Log.error(LocaleUtils.getLocalizedString("admin.error"),e);
273                     } catch (UnauthorizedException e) {
274                         Log.error(LocaleUtils.getLocalizedString("admin.error"),e);
275                     }
276                 }
277             }
278             catch (SQLException e) {
279                 Log.error(LocaleUtils.getLocalizedString("admin.error"),e);
280             }
281             */

282         }
283     }
284
285     /**
286      * Notification method sent to this handler when a user has sent a directed
287      * presence to an entity. If the sender of the presence is local (to this server)
288      * and the target entity does not belong to the user's roster then update the
289      * registry of sent directed presences by the user.
290      *
291      * @param update the directed Presence sent by the user to an entity.
292      * @param handler the handler that routed the presence to the entity.
293      * @param jid the jid that the handler has processed
294      */

295     public void directedPresenceSent(Presence update, ChannelHandler handler, String JavaDoc jid) {
296         if (update.getFrom() == null) {
297             return;
298         }
299         if (localServer.isLocal(update.getFrom())) {
300             boolean keepTrack = false;
301             WeakHashMap<ChannelHandler, Set<String JavaDoc>> map;
302             String JavaDoc name = update.getFrom().getNode();
303             if (name != null && !"".equals(name)) {
304                 name = name.toLowerCase();
305                 try {
306                     Roster roster = rosterManager.getRoster(name);
307                     // If the directed presence was sent to an entity that is not in the user's
308
// roster, keep a registry of this so that when the user goes offline we will
309
// be able to send the unavailable presence to the entity
310
if (!roster.isRosterItem(update.getTo())) {
311                         keepTrack = true;
312                     }
313                 }
314                 catch (UserNotFoundException e) {
315                     Log.warn("Presence being sent from unknown user " + name, e);
316                 }
317                 catch (PacketException e) {
318                     Log.error(LocaleUtils.getLocalizedString("admin.error"), e);
319                 }
320
321             }
322             else if (update.getFrom().getResource() != null){
323                 // Keep always track of anonymous users directed presences
324
keepTrack = true;
325             }
326             if (keepTrack) {
327                 map = directedPresences.get(update.getFrom().toString());
328                 if (map == null) {
329                     // We are using a set to avoid duplicate jids in case the user
330
// sends several directed presences to the same handler. The Map also
331
// ensures that if the user sends several presences to the same handler
332
// we will have only one entry in the Map
333
map = new WeakHashMap<ChannelHandler, Set<String JavaDoc>>();
334                     map.put(handler, new ConcurrentHashSet<String JavaDoc>());
335                     directedPresences.put(update.getFrom().toString(), map);
336                 }
337                 if (Presence.Type.unavailable.equals(update.getType())) {
338                     // It's a directed unavailable presence
339
if (handler instanceof ClientSession) {
340                         // Client sessions will receive only presences to the same JID (the
341
// address of the session) so remove the handler from the map
342
map.remove(handler);
343                         if (map.isEmpty()) {
344                             // Remove the user from the registry since the list of directed
345
// presences is empty
346
directedPresences.remove(update.getFrom().toString());
347                         }
348                     }
349                     else {
350                         // A service may receive presences for many JIDs so in this case we
351
// just need to remove the jid that has received a directed
352
// unavailable presence
353
Set<String JavaDoc> jids = map.get(handler);
354                         if (jids != null) {
355                             jids.remove(jid);
356                         }
357
358                     }
359                 }
360                 else {
361                     // Add the handler to the list of handler that processed the directed
362
// presence sent by the user. This handler will be used to send
363
// the unavailable presence when the user goes offline
364
if (map.get(handler) == null) {
365                         map.put(handler, new ConcurrentHashSet<String JavaDoc>());
366                     }
367                     map.get(handler).add(jid);
368                 }
369             }
370         }
371     }
372
373     /**
374      * Sends an unavailable presence to the entities that received a directed (available) presence
375      * by the user that is now going offline.
376      *
377      * @param update the unavailable presence sent by the user.
378      */

379     private void broadcastUnavailableForDirectedPresences(Presence update) {
380         if (update.getFrom() == null) {
381             return;
382         }
383         if (localServer.isLocal(update.getFrom())) {
384             Map JavaDoc<ChannelHandler, Set<String JavaDoc>> map = directedPresences.get(update.getFrom().toString());
385             if (map != null) {
386                 // Iterate over all the entities that the user sent a directed presence
387
for (ChannelHandler handler : new HashSet<ChannelHandler>(map.keySet())) {
388                     Set<String JavaDoc> jids = map.get(handler);
389                     if (jids == null) {
390                         continue;
391                     }
392                     for (String JavaDoc jid : jids) {
393                         Presence presence = update.createCopy();
394                         presence.setTo(new JID(jid));
395                         try {
396                             handler.process(presence);
397                         }
398                         catch (UnauthorizedException ue) {
399                             Log.error(ue);
400                         }
401                     }
402                 }
403                 // Remove the registry of directed presences of this user
404
directedPresences.remove(update.getFrom().toString());
405             }
406         }
407     }
408
409     public void initialize(XMPPServer server) {
410         super.initialize(server);
411         localServer = server;
412         rosterManager = server.getRosterManager();
413         presenceManager = server.getPresenceManager();
414         deliverer = server.getPacketDeliverer();
415         messageStore = server.getOfflineMessageStore();
416         sessionManager = server.getSessionManager();
417     }
418 }
419
Popular Tags