KickJava   Java API By Example, From Geeks To Geeks.

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


1 /**
2  * $RCSfile: PresenceSubscribeHandler.java,v $
3  * $Revision: 1.20 $
4  * $Date: 2005/07/01 21:24:58 $
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.container.BasicModule;
16 import org.jivesoftware.messenger.roster.Roster;
17 import org.jivesoftware.messenger.roster.RosterItem;
18 import org.jivesoftware.messenger.user.UserAlreadyExistsException;
19 import org.jivesoftware.messenger.user.UserNotFoundException;
20 import org.jivesoftware.util.CacheManager;
21 import org.jivesoftware.util.LocaleUtils;
22 import org.jivesoftware.util.Log;
23 import org.xmpp.packet.JID;
24 import org.xmpp.packet.Packet;
25 import org.xmpp.packet.PacketError;
26 import org.xmpp.packet.Presence;
27
28 import java.util.Hashtable JavaDoc;
29 import java.util.Map JavaDoc;
30
31 /**
32  * Implements the presence protocol. Clients use this protocol to
33  * update presence and roster information.
34  * <p/>
35  * The handler must properly detect the presence type, update the user's roster,
36  * and inform presence subscribers of the session's updated presence
37  * status. Presence serves many purposes in Jabber so this handler will
38  * likely be the most complex of all handlers in the server.
39  * <p/>
40  * There are four basic types of presence updates:
41  * <ul>
42  * <li>Simple presence updates - addressed to the server (or to address), these updates
43  * are properly addressed by the server, and multicast to
44  * interested subscribers on the user's roster. An empty, missing,
45  * or "unavailable" type attribute indicates a simple update (there
46  * is no "available" type although it should be accepted by the server.
47  * <li>Directed presence updates - addressed to particular jabber entities,
48  * these presence updates are properly addressed and directly delivered
49  * to the entity without broadcast to roster subscribers. Any update type
50  * is possible except those reserved for subscription requests.
51  * <li>Subscription requests - these updates request presence subscription
52  * status changes. Such requests always affect the roster. The server must:
53  * <ul>
54  * <li>update the roster with the proper subscriber info
55  * <li>push the roster changes to the user
56  * <li>forward the update to the correct parties.
57  * </ul>
58  * The valid types include "subscribe", "subscribed", "unsubscribed",
59  * and "unsubscribe".
60  * <li>XMPPServer probes - Provides a mechanism for servers to query the presence
61  * status of users on another server. This allows users to immediately
62  * know the presence status of users when they come online rather than way
63  * for a presence update broadcast from the other server or tracking them
64  * as they are received. Requires S2S capabilities.
65  * </ul>
66  * <p/>
67  * <h2>Warning</h2>
68  * There should be a way of determining whether a session has
69  * authorization to access this feature. I'm not sure it is a good
70  * idea to do authorization in each handler. It would be nice if
71  * the framework could assert authorization policies across channels.
72  *
73  * @author Iain Shigeoka
74  */

75 public class PresenceSubscribeHandler extends BasicModule implements ChannelHandler {
76
77     private RoutingTable routingTable;
78     private XMPPServer localServer;
79     private PacketDeliverer deliverer;
80     private PresenceManager presenceManager;
81
82     public PresenceSubscribeHandler() {
83         super("Presence subscription handler");
84     }
85
86     public void process(Packet xmppPacket) throws PacketException {
87         Presence presence = (Presence)xmppPacket;
88         try {
89             JID senderJID = presence.getFrom();
90             JID recipientJID = presence.getTo();
91             Presence.Type type = presence.getType();
92             try {
93                 Roster senderRoster = getRoster(senderJID);
94                 boolean senderSubChanged = false;
95                 if (senderRoster != null) {
96                     senderSubChanged = manageSub(recipientJID, true, type, senderRoster);
97                 }
98                 Roster recipientRoster = getRoster(recipientJID);
99                 boolean recipientSubChanged = false;
100                 if (recipientRoster != null) {
101                     recipientSubChanged = manageSub(senderJID, false, type, recipientRoster);
102                 }
103
104                 // Do not forward the packet to the recipient if the presence is of type subscribed
105
// and the recipient user has not changed its subscription state.
106
if (!(type == Presence.Type.subscribed && recipientRoster != null &&
107                         !recipientSubChanged)) {
108
109                     // If the user is already subscribed to the *local* user's presence then do not
110
// forward the subscription request and instead send an auto-reply on behalf
111
// of the user
112
if (type == Presence.Type.subscribe && recipientRoster != null &&
113                         !recipientSubChanged) {
114                         try {
115                             RosterItem.SubType subType = recipientRoster.getRosterItem(senderJID)
116                                     .getSubStatus();
117                             if (subType == RosterItem.SUB_FROM || subType == RosterItem.SUB_BOTH) {
118                                 // auto-reply by sending a presence stanza of type "subscribed"
119
// to the contact on behalf of the user
120
Presence reply = new Presence();
121                                 reply.setTo(senderJID);
122                                 reply.setFrom(recipientJID);
123                                 reply.setType(Presence.Type.subscribed);
124                                 deliverer.deliver(reply);
125                                 return;
126                             }
127                         }
128                         catch (UserNotFoundException e) {
129                             // Weird case: Roster item does not exist. Should never happen
130
}
131                     }
132
133                     // Try to obtain a handler for the packet based on the routes. If the handler is
134
// a module, the module will be able to handle the packet. If the handler is a
135
// Session the packet will be routed to the client. If a route cannot be found
136
// then the packet will be delivered based on its recipient and sender.
137
ChannelHandler handler = routingTable.getRoute(recipientJID);
138                     Presence presenteToSend = presence.createCopy();
139                     // Stamp the presence with the user's bare JID as the 'from' address
140
presenteToSend.setFrom(senderJID.toBareJID());
141                     handler.process(presenteToSend);
142
143                     if (type == Presence.Type.subscribed) {
144                         // Send the presence of the local user to the remote user. The remote user
145
// subscribed to the presence of the local user and the local user accepted
146
presenceManager.probePresence(recipientJID, senderJID);
147                     }
148                 }
149
150                 if (type == Presence.Type.unsubscribed) {
151                     // Send unavailable presence from all of the local user's available resources
152
// to the remote user
153
presenceManager.sendUnavailableFromSessions(recipientJID, senderJID);
154                 }
155             }
156             catch (NoSuchRouteException e) {
157                 deliverer.deliver(presence.createCopy());
158             }
159             catch (SharedGroupException e) {
160                 Presence result = presence.createCopy();
161                 JID sender = result.getFrom();
162                 result.setFrom(presence.getTo());
163                 result.setTo(sender);
164                 result.setError(PacketError.Condition.not_acceptable);
165                 deliverer.deliver(result);
166             }
167         }
168         catch (Exception JavaDoc e) {
169             Log.error(LocaleUtils.getLocalizedString("admin.error"), e);
170         }
171     }
172
173     /**
174      * <p>Obtain the roster for the given address or null if the address doesn't have a roster.</p>
175      *
176      * @param address The address to check
177      * @return The roster or null if the address is not managed on the server
178      */

179     private Roster getRoster(JID address) {
180         String JavaDoc username = null;
181         Roster roster = null;
182         if (localServer.isLocal(address) && address.getNode() != null &&
183                 !"".equals(address.getNode())) {
184             username = address.getNode();
185             // Check for a cached roster:
186
roster = (Roster)CacheManager.getCache("username2roster").get(username);
187             if (roster == null) {
188                 synchronized(address.toString().intern()) {
189                     roster = (Roster)CacheManager.getCache("username2roster").get(username);
190                     if (roster == null) {
191                         // Not in cache so load a new one:
192
roster = new Roster(username);
193                         CacheManager.getCache("username2roster").put(username, roster);
194                     }
195                 }
196             }
197         }
198         return roster;
199     }
200
201     /**
202      * Manage the subscription request. This method retrieves a user's roster
203      * and updates it's state, storing any changes made, and updating the roster
204      * owner if changes occured.
205      *
206      * @param target The roster target's jid (the item's jid to be changed)
207      * @param isSending True if the request is being sent by the owner
208      * @param type The subscription change type (subscribe, unsubscribe, etc.)
209      * @return true if the subscription state has changed.
210      */

211     private boolean manageSub(JID target, boolean isSending, Presence.Type type, Roster roster)
212             throws UserAlreadyExistsException, SharedGroupException
213     {
214         RosterItem item = null;
215         RosterItem.AskType oldAsk = null;
216         RosterItem.SubType oldSub = null;
217         RosterItem.RecvType oldRecv = null;
218         try {
219             if (roster.isRosterItem(target)) {
220                 item = roster.getRosterItem(target);
221             }
222             else {
223                 item = roster.createRosterItem(target);
224             }
225             // Get a snapshot of the item state
226
oldAsk = item.getAskStatus();
227             oldSub = item.getSubStatus();
228             oldRecv = item.getRecvStatus();
229             // Update the item state based in the received presence type
230
updateState(item, type, isSending);
231             // Update the roster IF the item state has changed
232
if (oldAsk != item.getAskStatus() || oldSub != item.getSubStatus() ||
233                     oldRecv != item.getRecvStatus()) {
234                 roster.updateRosterItem(item);
235             }
236         }
237         catch (UserNotFoundException e) {
238             // Should be there because we just checked that it's an item
239
Log.error(LocaleUtils.getLocalizedString("admin.error"), e);
240         }
241         return oldSub != item.getSubStatus();
242     }
243
244     /**
245      * <p>The transition state table.</p>
246      * <p>The root 'old state' transition table is a Map of RosterItem.SubType keys to match
247      * to the old state of the item. Each key returns a Map containing the next
248      * transition table. Transitions are defined as:</p>
249      * <ul>
250      * <li>'send/receive' table: Lookup whether this updates was sent or received: obtain 'action' table - key: Presence.Type subcribe action, value: Map (transition table).</li>
251      * <li>'new state' table: the changed item values</li>
252      * </ul>
253      */

254     private static Hashtable JavaDoc stateTable = new Hashtable JavaDoc();
255
256     static {
257         Hashtable JavaDoc subrTable;
258         Hashtable JavaDoc subsTable;
259         Hashtable JavaDoc sr;
260
261         sr = new Hashtable JavaDoc();
262         subrTable = new Hashtable JavaDoc();
263         subsTable = new Hashtable JavaDoc();
264         sr.put("recv", subrTable);
265         sr.put("send", subsTable);
266         stateTable.put(RosterItem.SUB_NONE, sr);
267         // Item wishes to subscribe from owner
268
// Set flag and update roster if this is a new state, this is the normal way to begin
269
// a roster subscription negotiation.
270
subrTable.put(Presence.Type.subscribe, new Change(RosterItem.RECV_SUBSCRIBE, null, null)); // no transition
271
// Item granted subscription to owner
272
// The item's state immediately goes from NONE to TO and ask is reset
273
subrTable.put(Presence.Type.subscribed, new Change(null, RosterItem.SUB_TO, RosterItem.ASK_NONE));
274         // Item wishes to unsubscribe from owner
275
// This makes no sense, there is no subscription to remove
276
subrTable.put(Presence.Type.unsubscribe, new Change(null, null, null));
277         // Owner has subscription to item revoked
278
// Valid response if item requested subscription and we're denying request
279
subrTable.put(Presence.Type.unsubscribed, new Change(null, null, RosterItem.ASK_NONE));
280         // Owner asking to subscribe to item this is the normal way to begin
281
// a roster subscription negotiation.
282
subsTable.put(Presence.Type.subscribe, new Change(null, null, RosterItem.ASK_SUBSCRIBE));
283         // Item granted a subscription from owner
284
subsTable.put(Presence.Type.subscribed, new Change(RosterItem.RECV_NONE, RosterItem.SUB_FROM, null));
285         // Owner asking to unsubscribe to item
286
// This makes no sense (there is no subscription to unsubscribe from)
287
subsTable.put(Presence.Type.unsubscribe, new Change(null, null, null));
288         // Item has subscription from owner revoked
289
// Valid response if item requested subscription and we're denying request
290
subsTable.put(Presence.Type.unsubscribed, new Change(RosterItem.RECV_NONE, null, null));
291
292         sr = new Hashtable JavaDoc();
293         subrTable = new Hashtable JavaDoc();
294         subsTable = new Hashtable JavaDoc();
295         sr.put("recv", subrTable);
296         sr.put("send", subsTable);
297         stateTable.put(RosterItem.SUB_FROM, sr);
298         // Owner asking to subscribe to item
299
// Set flag and update roster if this is a new state, this is the normal way to begin
300
// a mutual roster subscription negotiation.
301
subsTable.put(Presence.Type.subscribe, new Change(null, null, RosterItem.ASK_SUBSCRIBE));
302         // Item granted a subscription from owner
303
// This may be necessary if the recipient didn't get an earlier subscribed grant
304
// or as a denial of an unsubscribe request
305
subsTable.put(Presence.Type.subscribed, new Change(RosterItem.RECV_NONE, null, null));
306         // Owner asking to unsubscribe to item
307
// This makes no sense (there is no subscription to unsubscribe from)
308
subsTable.put(Presence.Type.unsubscribe, new Change(null, RosterItem.SUB_NONE, null));
309         // Item has subscription from owner revoked
310
// Immediately transition to NONE state
311
subsTable.put(Presence.Type.unsubscribed, new Change(RosterItem.RECV_NONE, RosterItem.SUB_NONE, null));
312         // Item wishes to subscribe from owner
313
// Item already has a subscription so only interesting if item had requested unsubscribe
314
// Set flag and update roster if this is a new state, this is the normal way to begin
315
// a mutual roster subscription negotiation.
316
subrTable.put(Presence.Type.subscribe, new Change(RosterItem.RECV_NONE, null, null));
317         // Item granted subscription to owner
318
// The item's state immediately goes from FROM to BOTH and ask is reset
319
subrTable.put(Presence.Type.subscribed, new Change(null, RosterItem.SUB_BOTH, RosterItem.ASK_NONE));
320         // Item wishes to unsubscribe from owner
321
// This is the normal mechanism of removing subscription
322
subrTable.put(Presence.Type.unsubscribe, new Change(RosterItem.RECV_UNSUBSCRIBE, RosterItem.SUB_NONE, null));
323         // Owner has subscription to item revoked
324
// Valid response if owner requested subscription and item is denying request
325
subrTable.put(Presence.Type.unsubscribed, new Change(null, null, RosterItem.ASK_NONE));
326
327         sr = new Hashtable JavaDoc();
328         subrTable = new Hashtable JavaDoc();
329         subsTable = new Hashtable JavaDoc();
330         sr.put("recv", subrTable);
331         sr.put("send", subsTable);
332         stateTable.put(RosterItem.SUB_TO, sr);
333         // Owner asking to subscribe to item
334
// We're already subscribed, may be trying to unset a unsub request
335
subsTable.put(Presence.Type.subscribe, new Change(null, null, RosterItem.ASK_NONE));
336         // Item granted a subscription from owner
337
// Sets mutual subscription
338
subsTable.put(Presence.Type.subscribed, new Change(RosterItem.RECV_NONE, RosterItem.SUB_BOTH, null));
339         // Owner asking to unsubscribe to item
340
// Normal method of removing subscription
341
subsTable.put(Presence.Type.unsubscribe, new Change(null, RosterItem.SUB_NONE, RosterItem.ASK_UNSUBSCRIBE));
342         // Item has subscription from owner revoked
343
// No subscription to unsub, makes sense if denying subscription request or for
344
// situations where the original unsubscribed got lost
345
subsTable.put(Presence.Type.unsubscribed, new Change(RosterItem.RECV_NONE, null, null));
346         // Item wishes to subscribe from owner
347
// This is the normal way to negotiate a mutual subscription
348
subrTable.put(Presence.Type.subscribe, new Change(RosterItem.RECV_SUBSCRIBE, null, null));
349         // Item granted subscription to owner
350
// Owner already subscribed to item, could be a unsub denial or a lost packet
351
subrTable.put(Presence.Type.subscribed, new Change(null, null, RosterItem.ASK_NONE));
352         // Item wishes to unsubscribe from owner
353
// There is no subscription. May be trying to cancel earlier subscribe request.
354
subrTable.put(Presence.Type.unsubscribe, new Change(RosterItem.RECV_NONE, RosterItem.SUB_NONE, null));
355         // Owner has subscription to item revoked
356
// Setting subscription to none
357
subrTable.put(Presence.Type.unsubscribed, new Change(null, RosterItem.SUB_NONE, RosterItem.ASK_NONE));
358
359         sr = new Hashtable JavaDoc();
360         subrTable = new Hashtable JavaDoc();
361         subsTable = new Hashtable JavaDoc();
362         sr.put("recv", subrTable);
363         sr.put("send", subsTable);
364         stateTable.put(RosterItem.SUB_BOTH, sr);
365         // Owner asking to subscribe to item
366
// Makes sense if trying to cancel previous unsub request
367
subsTable.put(Presence.Type.subscribe, new Change(null, null, RosterItem.ASK_NONE));
368         // Item granted a subscription from owner
369
// This may be necessary if the recipient didn't get an earlier subscribed grant
370
// or as a denial of an unsubscribe request
371
subsTable.put(Presence.Type.subscribed, new Change(RosterItem.RECV_NONE, null, null));
372         // Owner asking to unsubscribe to item
373
// Set flags
374
subsTable.put(Presence.Type.unsubscribe, new Change(null, RosterItem.SUB_FROM, RosterItem.ASK_UNSUBSCRIBE));
375         // Item has subscription from owner revoked
376
// Immediately transition them to TO state
377
subsTable.put(Presence.Type.unsubscribed, new Change(RosterItem.RECV_NONE, RosterItem.SUB_TO, null));
378         // Item wishes to subscribe to owner
379
// Item already has a subscription so only interesting if item had requested unsubscribe
380
// Set flag and update roster if this is a new state, this is the normal way to begin
381
// a mutual roster subscription negotiation.
382
subrTable.put(Presence.Type.subscribe, new Change(RosterItem.RECV_NONE, null, null));
383         // Item granted subscription to owner
384
// Redundant unless denying unsub request
385
subrTable.put(Presence.Type.subscribed, new Change(null, null, RosterItem.ASK_NONE));
386         // Item wishes to unsubscribe from owner
387
// This is the normal mechanism of removing subscription
388
subrTable.put(Presence.Type.unsubscribe, new Change(RosterItem.RECV_UNSUBSCRIBE, RosterItem.SUB_TO, null));
389         // Owner has subscription to item revoked
390
// Immediately downgrade state to FROM
391
subrTable.put(Presence.Type.unsubscribed, new Change(RosterItem.RECV_NONE, RosterItem.SUB_FROM, RosterItem.ASK_NONE));
392     }
393
394     /**
395      * <p>Indicate a state change.</p>
396      * <p>Use nulls to indicate fields that should not be changed.</p>
397      */

398     private static class Change {
399         public Change(RosterItem.RecvType recv, RosterItem.SubType sub, RosterItem.AskType ask) {
400             newRecv = recv;
401             newSub = sub;
402             newAsk = ask;
403         }
404
405         public RosterItem.RecvType newRecv;
406         public RosterItem.SubType newSub;
407         public RosterItem.AskType newAsk;
408     }
409
410     /**
411      * Determine and call the update method based on the item's subscription state.
412      * The method also turns the action and sending status into an integer code
413      * for easier processing (switch statements).
414      * <p/>
415      * Code relies on states being in numerical order without skipping.
416      * In addition, the receive states must parallel the send states
417      * so that (send state X) + STATE_RECV_SUBSCRIBE == (receive state X)
418      * where X is subscribe, subscribed, etc.
419      * </p>
420      *
421      * @param item The item to be updated
422      * @param action The new state change request
423      * @param isSending True if the roster owner of the item is sending the new state change request
424      */

425     private static void updateState(RosterItem item, Presence.Type action, boolean isSending) {
426         Map JavaDoc srTable = (Map JavaDoc)stateTable.get(item.getSubStatus());
427         Map JavaDoc changeTable = (Map JavaDoc)srTable.get(isSending ? "send" : "recv");
428         Change change = (Change)changeTable.get(action);
429         boolean modified = false;
430         if (change.newAsk != null && change.newAsk != item.getAskStatus()) {
431             item.setAskStatus(change.newAsk);
432         }
433         if (change.newSub != null && change.newSub != item.getSubStatus()) {
434             item.setSubStatus(change.newSub);
435             modified = true;
436         }
437         if (change.newRecv != null && change.newRecv != item.getRecvStatus()) {
438             item.setRecvStatus(change.newRecv);
439         }
440     }
441
442     public void initialize(XMPPServer server) {
443         super.initialize(server);
444         localServer = server;
445         routingTable = server.getRoutingTable();
446         deliverer = server.getPacketDeliverer();
447         presenceManager = server.getPresenceManager();
448     }
449 }
Popular Tags