1 /*
2  * Copyright (C) 2004-2008 Jive Software. All rights reserved.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package org.jivesoftware.openfire.handler;
18 
19 import java.util.Hashtable;
20 import java.util.List;
21 import java.util.Map;
22 
23 import org.jivesoftware.openfire.ChannelHandler;
24 import org.jivesoftware.openfire.PacketDeliverer;
25 import org.jivesoftware.openfire.PacketException;
26 import org.jivesoftware.openfire.PresenceManager;
27 import org.jivesoftware.openfire.RoutingTable;
28 import org.jivesoftware.openfire.SharedGroupException;
29 import org.jivesoftware.openfire.XMPPServer;
30 import org.jivesoftware.openfire.container.BasicModule;
31 import org.jivesoftware.openfire.roster.Roster;
32 import org.jivesoftware.openfire.roster.RosterItem;
33 import org.jivesoftware.openfire.roster.RosterManager;
34 import org.jivesoftware.openfire.user.PresenceEventDispatcher;
35 import org.jivesoftware.openfire.user.UserAlreadyExistsException;
36 import org.jivesoftware.openfire.user.UserManager;
37 import org.jivesoftware.openfire.user.UserNotFoundException;
38 import org.jivesoftware.util.LocaleUtils;
39 import org.slf4j.Logger;
40 import org.slf4j.LoggerFactory;
41 import org.xmpp.packet.JID;
42 import org.xmpp.packet.PacketError;
43 import org.xmpp.packet.Presence;
44 
45 /**
46  * Implements the presence protocol. Clients use this protocol to
47  * update presence and roster information.
48  * <p>
49  * The handler must properly detect the presence type, update the user's roster,
50  * and inform presence subscribers of the session's updated presence
51  * status. Presence serves many purposes in Jabber so this handler will
52  * likely be the most complex of all handlers in the server.</p>
53  * <p>
54  * There are four basic types of presence updates:</p>
55  * <ul>
56  * <li>Simple presence updates - addressed to the server (or to address), these updates
57  * are properly addressed by the server, and multicast to
58  * interested subscribers on the user's roster. An empty, missing,
59  * or "unavailable" type attribute indicates a simple update (there
60  * is no "available" type although it should be accepted by the server.
61  * <li>Directed presence updates - addressed to particular jabber entities,
62  * these presence updates are properly addressed and directly delivered
63  * to the entity without broadcast to roster subscribers. Any update type
64  * is possible except those reserved for subscription requests.
65  * <li>Subscription requests - these updates request presence subscription
66  * status changes. Such requests always affect the roster.  The server must:
67  * <ul>
68  * <li>update the roster with the proper subscriber info
69  * <li>push the roster changes to the user
70  * <li>forward the update to the correct parties.
71  * </ul>
72  * The valid types include "subscribe", "subscribed", "unsubscribed",
73  * and "unsubscribe".
74  * <li>XMPPServer probes - Provides a mechanism for servers to query the presence
75  * status of users on another server. This allows users to immediately
76  * know the presence status of users when they come online rather than way
77  * for a presence update broadcast from the other server or tracking them
78  * as they are received.  Requires S2S capabilities.
79  * </ul>
80  * <h2>Warning</h2>
81  * There should be a way of determining whether a session has
82  * authorization to access this feature. I'm not sure it is a good
83  * idea to do authorization in each handler. It would be nice if
84  * the framework could assert authorization policies across channels.
85  *
86  * @author Iain Shigeoka
87  */
88 public class PresenceSubscribeHandler extends BasicModule implements ChannelHandler<Presence> {
89 
90     private static final Logger Log = LoggerFactory.getLogger(PresenceSubscribeHandler.class);
91 
92     private RoutingTable routingTable;
93     private XMPPServer localServer;
94     private String serverName;
95     private PacketDeliverer deliverer;
96     private PresenceManager presenceManager;
97     private RosterManager rosterManager;
98     private UserManager userManager;
99 
PresenceSubscribeHandler()100     public PresenceSubscribeHandler() {
101         super("Presence subscription handler");
102     }
103 
104     @Override
process(Presence presence)105     public void process(Presence presence) throws PacketException {
106         if (presence == null) {
107             throw new IllegalArgumentException("Argument 'presence' cannot be null.");
108         }
109 
110         final Presence.Type type = presence.getType();
111 
112         if (type != Presence.Type.subscribe
113                 && type != Presence.Type.unsubscribe
114                 && type != Presence.Type.subscribed
115                 && type != Presence.Type.unsubscribed) {
116             throw new IllegalArgumentException("Packet processed by PresenceSubscribeHandler is "
117                     + "not of a subscription-related type, but: " + type);
118         }
119 
120         // RFC-6121 paragraph 3: "When a server processes or generates an outbound presence stanza
121         // of type "subscribe", "subscribed", "unsubscribe", or "unsubscribed", the server MUST stamp
122         // the outgoing presence stanza with the bare JID <localpart@domainpart> of the sending entity,
123         // not the full JID <localpart@domainpart/resourcepart>."
124         presence.setFrom(presence.getFrom().toBareJID());
125 
126         // RFC-6121 paragraph 3.1.3: "Before processing the inbound presence subscription request, the
127         // contact's server SHOULD check the syntax of the JID contained in the 'to' attribute. If the
128         // JID is of the form <contact@domainpart/resourcepart> instead of <contact@domainpart>, the
129         // contact's server SHOULD treat it as if the request had been directed to the contact's bare
130         // JID and modify the 'to' address accordingly.
131         if (presence.getTo() != null) {
132             presence.setTo(presence.getTo().toBareJID());
133         }
134 
135         final JID senderJID = presence.getFrom();
136         final JID recipientJID = presence.getTo();
137 
138         try {
139             // Reject presence subscription requests sent to the local server itself.
140             if (recipientJID == null || recipientJID.toString().equals(serverName)) {
141                 if (type == Presence.Type.subscribe) {
142                     Presence reply = new Presence();
143                     reply.setTo(senderJID);
144                     reply.setFrom(recipientJID);
145                     reply.setType(Presence.Type.unsubscribed);
146                     deliverer.deliver(reply);
147                 }
148                 return;
149             }
150 
151             try {
152                 Roster senderRoster = getRoster(senderJID);
153                 if (senderRoster != null) {
154                     manageSub(recipientJID, true, presence, senderRoster);
155                 }
156                 Roster recipientRoster = getRoster(recipientJID);
157                 boolean recipientSubChanged = false;
158                 if (recipientRoster != null) {
159                     recipientSubChanged = manageSub(senderJID, false, presence, recipientRoster);
160                 }
161 
162                 // Do not forward the packet to the recipient if the presence is of type subscribed
163                 // and the recipient user has not changed its subscription state.
164                 if (!(type == Presence.Type.subscribed && recipientRoster != null && !recipientSubChanged)) {
165 
166                     // If the user is already subscribed to the *local* user's presence then do not
167                     // forward the subscription request. Also, do not send an auto-reply on behalf
168                     // of the user. This presence stanza is the user's server know that it MUST no
169                     // longer send notification of the subscription state change to the user.
170                     // See http://tools.ietf.org/html/rfc3921#section-7 and/or OF-38
171                     if (type == Presence.Type.subscribe && recipientRoster != null && !recipientSubChanged) {
172                         try {
173                             RosterItem.SubType subType = recipientRoster.getRosterItem(senderJID)
174                                     .getSubStatus();
175                             if (subType == RosterItem.SUB_FROM || subType == RosterItem.SUB_BOTH) {
176                                 return;
177                             }
178                         }
179                         catch (UserNotFoundException e) {
180                             // Weird case: Roster item does not exist. Should never happen
181                             Log.error("User does not exist while trying to update roster item. " +
182                                     "This should never happen (this indicates a programming " +
183                                     "logic error). Processing stanza: " + presence.toString(), e);
184                         }
185                     }
186 
187                     // Try to obtain a handler for the packet based on the routes. If the handler is
188                     // a module, the module will be able to handle the packet. If the handler is a
189                     // Session the packet will be routed to the client. If a route cannot be found
190                     // then the packet will be delivered based on its recipient and sender.
191                     List<JID> jids = routingTable.getRoutes(recipientJID, null);
192                     if (!jids.isEmpty()) {
193                         for (JID jid : jids) {
194                             Presence presenteToSend = presence.createCopy();
195                             // Stamp the presence with the user's bare JID as the 'from' address,
196                             // as required by section 8.2.5 of RFC 3921
197                             presenteToSend.setFrom(senderJID.toBareJID());
198                             routingTable.routePacket(jid, presenteToSend, false);
199                         }
200                     }
201                     else {
202                         deliverer.deliver(presence.createCopy());
203                     }
204 
205                     if (type == Presence.Type.subscribed) {
206                         // Send the presence of the newly subscribed contact to the subscribee, by doing a presence probe.
207                         JID prober = localServer.isLocal(recipientJID) ? recipientJID.asBareJID() : recipientJID;
208                         if (localServer.isLocal(senderJID) && !presenceManager.canProbePresence(prober, senderJID.getNode())){
209                             Presence nonProbablePresence = new Presence();
210                             nonProbablePresence.setStatus("unavailable");
211                             nonProbablePresence.setFrom(senderJID);
212                             nonProbablePresence.setTo(recipientJID);
213                             presenceManager.handleProbe(nonProbablePresence);
214                         }
215                         else {
216                             presenceManager.probePresence(prober, senderJID);
217                             PresenceEventDispatcher.subscribedToPresence(recipientJID, senderJID);
218                         }
219                     }
220                 }
221 
222                 if (type == Presence.Type.unsubscribed) {
223                     // Send unavailable presence from all of the local user's available resources
224                     // to the remote user
225                     presenceManager.sendUnavailableFromSessions(recipientJID, senderJID);
226                     PresenceEventDispatcher.unsubscribedToPresence(senderJID, recipientJID);
227                 }
228             }
229             catch (SharedGroupException e) {
230                 Presence result = presence.createCopy();
231                 JID sender = result.getFrom();
232                 result.setFrom(presence.getTo());
233                 result.setTo(sender);
234                 result.setError(PacketError.Condition.not_acceptable);
235                 deliverer.deliver(result);
236             }
237         }
238         catch (Exception e) {
239             Log.error(LocaleUtils.getLocalizedString("admin.error"), e);
240         }
241     }
242 
243     /**
244      * <p>Obtain the roster for the given address or null if the address doesn't have a roster.</p>
245      *
246      * @param address The address to check
247      * @return The roster or null if the address is not managed on the server
248      */
getRoster(JID address)249     private Roster getRoster(JID address) {
250         String username;
251         Roster roster = null;
252         if (userManager.isRegisteredUser(address, false)) {
253             username = address.getNode();
254             try {
255                 roster = rosterManager.getRoster(username);
256             }
257             catch (UserNotFoundException e) {
258                 // Do nothing
259             }
260         }
261         return roster;
262     }
263 
264     /**
265      * Manage the subscription request. This method updates a user's roster
266      * state, storing any changes made, and updating the roster owner if changes
267      * occured.
268      *
269      * @param target    The roster target's jid (the item's jid to be changed)
270      * @param isSending True if the request is being sent by the owner
271      * @param presence      The subscription stanza
272      * @param roster    The Roster that is updated.
273      * @return {@code true} if the subscription state has changed.
274      */
manageSub(JID target, boolean isSending, Presence presence, Roster roster)275     private boolean manageSub(JID target, boolean isSending, Presence presence, Roster roster)
276             throws UserAlreadyExistsException, SharedGroupException, IllegalArgumentException
277     {
278         RosterItem item = null;
279         RosterItem.AskType oldAsk;
280         RosterItem.SubType oldSub = null;
281         RosterItem.RecvType oldRecv;
282         Presence.Type type = presence.getType();
283         boolean newItem = false;
284         try {
285             if (roster.isRosterItem(target)) {
286                 item = roster.getRosterItem(target);
287             }
288             else {
289                 if (Presence.Type.unsubscribed == type || Presence.Type.unsubscribe == type ||
290                         Presence.Type.subscribed == type) {
291                     // Do not create a roster item when processing a confirmation of
292                     // an unsubscription or receiving an unsubscription request or a
293                     // subscription approval from an unknown user
294                     return false;
295                 }
296                 if (Presence.Type.subscribe != type) {
297                     throw new IllegalArgumentException("manageSub asked to manage non-subscription presence");
298                 }
299                 item = roster.createRosterItem(target, false, true);
300                 newItem = true;
301             }
302 
303             if (Presence.Type.subscribe == type && !isSending) {
304                 // For inbound <presence type='subscribe'/>, we should update the stored stanza here.
305                 item.setStoredSubscribeStanza(presence);
306                 RosterManager.getRosterItemProvider().updateItem(roster.getUsername(), item);
307             }
308 
309             // Get a snapshot of the item state
310             oldAsk = item.getAskStatus();
311             oldSub = item.getSubStatus();
312             oldRecv = item.getRecvStatus();
313             // Update the item state based in the received presence type
314             updateState(item, type, isSending);
315             // Update the roster IF the item state has changed
316             if (oldAsk != item.getAskStatus() || oldSub != item.getSubStatus() || oldRecv != item.getRecvStatus()) {
317                 if (oldRecv == RosterItem.RECV_SUBSCRIBE && item.getRecvStatus() != RosterItem.RECV_SUBSCRIBE) {
318                     // No longer asking for a subscription. Clean up the original subscription request stanza.
319                     item.setStoredSubscribeStanza(null);
320                 }
321                 roster.updateRosterItem(item);
322             }
323             else if (newItem) {
324                 // Do not push items with a state of "None + Pending In"
325                 if (item.getSubStatus() != RosterItem.SUB_NONE ||
326                         item.getRecvStatus() != RosterItem.RECV_SUBSCRIBE) {
327                     roster.broadcast(item, false);
328                 }
329             }
330         }
331         catch (UserNotFoundException e) {
332             // Should be there because we just checked that it's an item
333             Log.error(LocaleUtils.getLocalizedString("admin.error"), e);
334         }
335         return oldSub != item.getSubStatus();
336     }
337 
338     /**
339      * <p>The transition state table.</p>
340      * <p>The root 'old state' transition table is a Map of RosterItem.SubType keys to match
341      * to the old state of the item. Each key returns a Map containing the next
342      * transition table. Transitions are defined as:</p>
343      * <ul>
344      * <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>
345      * <li>'new state' table: the changed item values</li>
346      * </ul>
347      */
348     private static Hashtable<RosterItem.SubType, Map<String, Map<Presence.Type, Change>>> stateTable =
349             new Hashtable<>();
350 
351     static {
352         Hashtable<Presence.Type, Change> subrTable;
353         Hashtable<Presence.Type, Change> subsTable;
354         Hashtable<String,Map<Presence.Type, Change>> sr;
355 
356         sr = new Hashtable<>();
357         subrTable = new Hashtable<>();
358         subsTable = new Hashtable<>();
359         sr.put("recv", subrTable);
360         sr.put("send", subsTable);
stateTable.put(RosterItem.SUB_NONE, sr)361         stateTable.put(RosterItem.SUB_NONE, sr);
362         // Item wishes to subscribe from owner
363         // Set flag and update roster if this is a new state, this is the normal way to begin
364         // a roster subscription negotiation.
subrTable.put(Presence.Type.subscribe, new Change(RosterItem.RECV_SUBSCRIBE, null, null))365         subrTable.put(Presence.Type.subscribe, new Change(RosterItem.RECV_SUBSCRIBE, null, null)); // no transition
366         // Item granted subscription to owner
367         // The item's state immediately goes from NONE to TO and ask is reset
subrTable.put(Presence.Type.subscribed, new Change(null, RosterItem.SUB_TO, RosterItem.ASK_NONE))368         subrTable.put(Presence.Type.subscribed, new Change(null, RosterItem.SUB_TO, RosterItem.ASK_NONE));
369         // Item wishes to unsubscribe from owner
370         // This makes no sense, there is no subscription to remove
subrTable.put(Presence.Type.unsubscribe, new Change(null, null, null))371         subrTable.put(Presence.Type.unsubscribe, new Change(null, null, null));
372         // Owner has subscription to item revoked
373         // Valid response if item requested subscription and we're denying request
subrTable.put(Presence.Type.unsubscribed, new Change(null, null, RosterItem.ASK_NONE))374         subrTable.put(Presence.Type.unsubscribed, new Change(null, null, RosterItem.ASK_NONE));
375         // Owner asking to subscribe to item this is the normal way to begin
376         // a roster subscription negotiation.
subsTable.put(Presence.Type.subscribe, new Change(null, null, RosterItem.ASK_SUBSCRIBE))377         subsTable.put(Presence.Type.subscribe, new Change(null, null, RosterItem.ASK_SUBSCRIBE));
378         // Item granted a subscription from owner
subsTable.put(Presence.Type.subscribed, new Change(RosterItem.RECV_NONE, RosterItem.SUB_FROM, null))379         subsTable.put(Presence.Type.subscribed, new Change(RosterItem.RECV_NONE, RosterItem.SUB_FROM, null));
380         // Owner asking to unsubscribe to item
381         // This makes no sense (there is no subscription to unsubscribe from)
subsTable.put(Presence.Type.unsubscribe, new Change(null, null, null))382         subsTable.put(Presence.Type.unsubscribe, new Change(null, null, null));
383         // Item has subscription from owner revoked
384         // Valid response if item requested subscription and we're denying request
subsTable.put(Presence.Type.unsubscribed, new Change(RosterItem.RECV_NONE, null, null))385         subsTable.put(Presence.Type.unsubscribed, new Change(RosterItem.RECV_NONE, null, null));
386 
387         sr = new Hashtable<>();
388         subrTable = new Hashtable<>();
389         subsTable = new Hashtable<>();
390         sr.put("recv", subrTable);
391         sr.put("send", subsTable);
stateTable.put(RosterItem.SUB_FROM, sr)392         stateTable.put(RosterItem.SUB_FROM, sr);
393         // Owner asking to subscribe to item
394         // Set flag and update roster if this is a new state, this is the normal way to begin
395         // a mutual roster subscription negotiation.
subsTable.put(Presence.Type.subscribe, new Change(null, null, RosterItem.ASK_SUBSCRIBE))396         subsTable.put(Presence.Type.subscribe, new Change(null, null, RosterItem.ASK_SUBSCRIBE));
397         // Item granted a subscription from owner
398         // This may be necessary if the recipient didn't get an earlier subscribed grant
399         // or as a denial of an unsubscribe request
subsTable.put(Presence.Type.subscribed, new Change(RosterItem.RECV_NONE, null, null))400         subsTable.put(Presence.Type.subscribed, new Change(RosterItem.RECV_NONE, null, null));
401         // Owner asking to unsubscribe to item
402         // This makes no sense (there is no subscription to unsubscribe from)
subsTable.put(Presence.Type.unsubscribe, new Change(null, RosterItem.SUB_NONE, null))403         subsTable.put(Presence.Type.unsubscribe, new Change(null, RosterItem.SUB_NONE, null));
404         // Item has subscription from owner revoked
405         // Immediately transition to NONE state
subsTable.put(Presence.Type.unsubscribed, new Change(RosterItem.RECV_NONE, RosterItem.SUB_NONE, null))406         subsTable.put(Presence.Type.unsubscribed, new Change(RosterItem.RECV_NONE, RosterItem.SUB_NONE, null));
407         // Item wishes to subscribe from owner
408         // Item already has a subscription so only interesting if item had requested unsubscribe
409         // Set flag and update roster if this is a new state, this is the normal way to begin
410         // a mutual roster subscription negotiation.
subrTable.put(Presence.Type.subscribe, new Change(RosterItem.RECV_NONE, null, null))411         subrTable.put(Presence.Type.subscribe, new Change(RosterItem.RECV_NONE, null, null));
412         // Item granted subscription to owner
413         // The item's state immediately goes from FROM to BOTH and ask is reset
subrTable.put(Presence.Type.subscribed, new Change(null, RosterItem.SUB_BOTH, RosterItem.ASK_NONE))414         subrTable.put(Presence.Type.subscribed, new Change(null, RosterItem.SUB_BOTH, RosterItem.ASK_NONE));
415         // Item wishes to unsubscribe from owner
416         // This is the normal mechanism of removing subscription
subrTable.put(Presence.Type.unsubscribe, new Change(RosterItem.RECV_UNSUBSCRIBE, RosterItem.SUB_NONE, null))417         subrTable.put(Presence.Type.unsubscribe, new Change(RosterItem.RECV_UNSUBSCRIBE, RosterItem.SUB_NONE, null));
418         // Owner has subscription to item revoked
419         // Valid response if owner requested subscription and item is denying request
subrTable.put(Presence.Type.unsubscribed, new Change(null, null, RosterItem.ASK_NONE))420         subrTable.put(Presence.Type.unsubscribed, new Change(null, null, RosterItem.ASK_NONE));
421 
422         sr = new Hashtable<>();
423         subrTable = new Hashtable<>();
424         subsTable = new Hashtable<>();
425         sr.put("recv", subrTable);
426         sr.put("send", subsTable);
stateTable.put(RosterItem.SUB_TO, sr)427         stateTable.put(RosterItem.SUB_TO, sr);
428         // Owner asking to subscribe to item
429         // We're already subscribed, may be trying to unset a unsub request
subsTable.put(Presence.Type.subscribe, new Change(null, null, RosterItem.ASK_NONE))430         subsTable.put(Presence.Type.subscribe, new Change(null, null, RosterItem.ASK_NONE));
431         // Item granted a subscription from owner
432         // Sets mutual subscription
subsTable.put(Presence.Type.subscribed, new Change(RosterItem.RECV_NONE, RosterItem.SUB_BOTH, null))433         subsTable.put(Presence.Type.subscribed, new Change(RosterItem.RECV_NONE, RosterItem.SUB_BOTH, null));
434         // Owner asking to unsubscribe to item
435         // Normal method of removing subscription
subsTable.put(Presence.Type.unsubscribe, new Change(null, RosterItem.SUB_NONE, RosterItem.ASK_UNSUBSCRIBE))436         subsTable.put(Presence.Type.unsubscribe, new Change(null, RosterItem.SUB_NONE, RosterItem.ASK_UNSUBSCRIBE));
437         // Item has subscription from owner revoked
438         // No subscription to unsub, makes sense if denying subscription request or for
439         // situations where the original unsubscribed got lost
subsTable.put(Presence.Type.unsubscribed, new Change(RosterItem.RECV_NONE, null, null))440         subsTable.put(Presence.Type.unsubscribed, new Change(RosterItem.RECV_NONE, null, null));
441         // Item wishes to subscribe from owner
442         // This is the normal way to negotiate a mutual subscription
subrTable.put(Presence.Type.subscribe, new Change(RosterItem.RECV_SUBSCRIBE, null, null))443         subrTable.put(Presence.Type.subscribe, new Change(RosterItem.RECV_SUBSCRIBE, null, null));
444         // Item granted subscription to owner
445         // Owner already subscribed to item, could be a unsub denial or a lost packet
subrTable.put(Presence.Type.subscribed, new Change(null, null, RosterItem.ASK_NONE))446         subrTable.put(Presence.Type.subscribed, new Change(null, null, RosterItem.ASK_NONE));
447         // Item wishes to unsubscribe from owner
448         // There is no subscription. May be trying to cancel earlier subscribe request.
subrTable.put(Presence.Type.unsubscribe, new Change(RosterItem.RECV_NONE, RosterItem.SUB_NONE, null))449         subrTable.put(Presence.Type.unsubscribe, new Change(RosterItem.RECV_NONE, RosterItem.SUB_NONE, null));
450         // Owner has subscription to item revoked
451         // Setting subscription to none
subrTable.put(Presence.Type.unsubscribed, new Change(null, RosterItem.SUB_NONE, RosterItem.ASK_NONE))452         subrTable.put(Presence.Type.unsubscribed, new Change(null, RosterItem.SUB_NONE, RosterItem.ASK_NONE));
453 
454         sr = new Hashtable<>();
455         subrTable = new Hashtable<>();
456         subsTable = new Hashtable<>();
457         sr.put("recv", subrTable);
458         sr.put("send", subsTable);
stateTable.put(RosterItem.SUB_BOTH, sr)459         stateTable.put(RosterItem.SUB_BOTH, sr);
460         // Owner asking to subscribe to item
461         // Makes sense if trying to cancel previous unsub request
subsTable.put(Presence.Type.subscribe, new Change(null, null, RosterItem.ASK_NONE))462         subsTable.put(Presence.Type.subscribe, new Change(null, null, RosterItem.ASK_NONE));
463         // Item granted a subscription from owner
464         // This may be necessary if the recipient didn't get an earlier subscribed grant
465         // or as a denial of an unsubscribe request
subsTable.put(Presence.Type.subscribed, new Change(RosterItem.RECV_NONE, null, null))466         subsTable.put(Presence.Type.subscribed, new Change(RosterItem.RECV_NONE, null, null));
467         // Owner asking to unsubscribe to item
468         // Set flags
subsTable.put(Presence.Type.unsubscribe, new Change(null, RosterItem.SUB_FROM, RosterItem.ASK_UNSUBSCRIBE))469         subsTable.put(Presence.Type.unsubscribe, new Change(null, RosterItem.SUB_FROM, RosterItem.ASK_UNSUBSCRIBE));
470         // Item has subscription from owner revoked
471         // Immediately transition them to TO state
subsTable.put(Presence.Type.unsubscribed, new Change(RosterItem.RECV_NONE, RosterItem.SUB_TO, null))472         subsTable.put(Presence.Type.unsubscribed, new Change(RosterItem.RECV_NONE, RosterItem.SUB_TO, null));
473         // Item wishes to subscribe to owner
474         // Item already has a subscription so only interesting if item had requested unsubscribe
475         // Set flag and update roster if this is a new state, this is the normal way to begin
476         // a mutual roster subscription negotiation.
subrTable.put(Presence.Type.subscribe, new Change(RosterItem.RECV_NONE, null, null))477         subrTable.put(Presence.Type.subscribe, new Change(RosterItem.RECV_NONE, null, null));
478         // Item granted subscription to owner
479         // Redundant unless denying unsub request
subrTable.put(Presence.Type.subscribed, new Change(null, null, RosterItem.ASK_NONE))480         subrTable.put(Presence.Type.subscribed, new Change(null, null, RosterItem.ASK_NONE));
481         // Item wishes to unsubscribe from owner
482         // This is the normal mechanism of removing subscription
subrTable.put(Presence.Type.unsubscribe, new Change(RosterItem.RECV_UNSUBSCRIBE, RosterItem.SUB_TO, null))483         subrTable.put(Presence.Type.unsubscribe, new Change(RosterItem.RECV_UNSUBSCRIBE, RosterItem.SUB_TO, null));
484         // Owner has subscription to item revoked
485         // Immediately downgrade state to FROM
subrTable.put(Presence.Type.unsubscribed, new Change(RosterItem.RECV_NONE, RosterItem.SUB_FROM, RosterItem.ASK_NONE))486         subrTable.put(Presence.Type.unsubscribed, new Change(RosterItem.RECV_NONE, RosterItem.SUB_FROM, RosterItem.ASK_NONE));
487     }
488 
489     /**
490      * <p>Indicate a state change.</p>
491      * <p>Use nulls to indicate fields that should not be changed.</p>
492      */
493     public static class Change {
Change(RosterItem.RecvType recv, RosterItem.SubType sub, RosterItem.AskType ask)494         public Change(RosterItem.RecvType recv, RosterItem.SubType sub, RosterItem.AskType ask) {
495             newRecv = recv;
496             newSub = sub;
497             newAsk = ask;
498         }
499 
getNewRecv()500         public RosterItem.RecvType getNewRecv() { return newRecv; }
getNewSub()501         public RosterItem.SubType  getNewSub()  { return newSub; }
getNewAsk()502         public RosterItem.AskType  getNewAsk()  { return newAsk; }
503 
504         private RosterItem.RecvType newRecv;
505         private RosterItem.SubType newSub;
506         private RosterItem.AskType newAsk;
507     }
508 
509     /**
510      * Determine the changes to apply to the item, according to its subscription state.
511      * The method also turns the action and sending status into an integer code
512      * for easier processing (switch statements).
513      * <p>
514      * Code relies on states being in numerical order without skipping.
515      * In addition, the receive states must parallel the send states
516      * so that (send state X) + STATE_RECV_SUBSCRIBE == (receive state X)
517      * where X is subscribe, subscribed, etc.
518      * </p>
519      *
520      * @param  itemSubType The item to be updated
521      * @param  action      The new state change request
522      * @param  isSending   True if the roster owner of the item is sending the new state change request
523      * @return Change      changes to apply to the item
524      */
getStateChange(RosterItem.SubType itemSubType, Presence.Type action, boolean isSending)525     public static Change getStateChange(RosterItem.SubType itemSubType, Presence.Type action, boolean isSending) {
526         Map<String, Map<Presence.Type, Change>> srTable = stateTable.get(itemSubType);
527         Map<Presence.Type, Change> changeTable = srTable.get(isSending ? "send" : "recv");
528         return changeTable.get(action);
529     }
530 
531     /**
532      * Determine and call the update method based on the item's subscription state.
533      * <p/>
534      *
535      * @param item      The item to be updated
536      * @param action    The new state change request
537      * @param isSending True if the roster owner of the item is sending the new state change request
538      */
updateState(RosterItem item, Presence.Type action, boolean isSending)539     private static void updateState(RosterItem item, Presence.Type action, boolean isSending) {
540         Change change = getStateChange(item.getSubStatus(), action, isSending);
541         if (change.getNewAsk() != null && change.getNewAsk() != item.getAskStatus()) {
542             item.setAskStatus(change.getNewAsk());
543         }
544         if (change.getNewSub() != null && change.getNewSub() != item.getSubStatus()) {
545             item.setSubStatus(change.getNewSub());
546         }
547         if (change.getNewRecv() != null && change.getNewRecv() != item.getRecvStatus()) {
548             item.setRecvStatus(change.getNewRecv());
549         }
550     }
551 
552     @Override
initialize(XMPPServer server)553     public void initialize(XMPPServer server) {
554         super.initialize(server);
555         localServer = server;
556         serverName = server.getServerInfo().getXMPPDomain();
557         routingTable = server.getRoutingTable();
558         deliverer = server.getPacketDeliverer();
559         presenceManager = server.getPresenceManager();
560         rosterManager = server.getRosterManager();
561         userManager = server.getUserManager();
562     }
563 }
564