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