Commit fa6beb91 authored by Guus der Kinderen's avatar Guus der Kinderen Committed by daryl herzmann

OF-210: Additional work. (#923)

* Support for Roster Versioning (without send the modifications via roster pushes)

* Roster versioning comparison clearing

* Implementation Note: This empty IQ-result is different from an empty <query/> element, thus disambiguating this usage from an empty roster.

* Avoid cache miss while updating roster

When the roster is updated via group renaming, group user adding or removing, the roster pushes only happen if there is a cache hit. If there is a cache miss (what can happen if the cache is full or if the admin cleaned up the cache) the user is not properly notified about the roster update. Thus only update rosters in memory can lead to this undesired behavior.

This commit avoids the use of the cache directly (where there can be a cache miss or a cache hit). It is using the method getRoster(username) that instantiante a new Roster in the case of a cache miss.

* Clarify the code

* OF-210: Base roster version on its hashCode.

This commit removes all fields from the Roster class that do not relate to its state
(replacing them with method variables - which seems harmless, as they're all final
singletons). This allows for an easy override of Object#hashCode() and equals().
These, in turn, are used to calculate the roster version from.

* Simplified loop

* Prevent potential NPEs.

* Log exceptions for exceptions that cannot happen.

If they cannot happen, we should scream murder if they do...

* OF-210: Roster versioning enabled by default.
parent 1494c6b2
......@@ -182,7 +182,23 @@ public class IQRosterHandler extends IQHandler implements ServerFeaturesProvider
Roster cachedRoster = userManager.getUser(sender.getNode()).getRoster();
if (IQ.Type.get == type) {
if (RosterManager.isRosterVersioningEnabled()) {
String clientVersion = packet.getChildElement().attributeValue("ver");
String latestVersion = String.valueOf( cachedRoster.hashCode() );
// Whether or not the roster has been modified since the version ID enumerated by the client, ...
if (!latestVersion.equals(clientVersion)) {
// ... the server MUST either return the complete roster
// (including a 'ver' attribute that signals the latest version)
returnPacket = cachedRoster.getReset();
returnPacket.getChildElement().addAttribute("ver", latestVersion );
} else {
// ... or return an empty IQ-result
returnPacket = new org.xmpp.packet.IQ();
}
} else {
returnPacket = cachedRoster.getReset();
}
returnPacket.setType(IQ.Type.result);
returnPacket.setTo(sender);
returnPacket.setID(packet.getID());
......
......@@ -16,27 +16,8 @@
package org.jivesoftware.openfire.roster;
import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import org.jivesoftware.database.JiveID;
import org.jivesoftware.openfire.PresenceManager;
import org.jivesoftware.openfire.RoutingTable;
import org.jivesoftware.openfire.SessionManager;
import org.jivesoftware.openfire.SharedGroupException;
import org.jivesoftware.openfire.XMPPServer;
import org.jivesoftware.openfire.*;
import org.jivesoftware.openfire.group.Group;
import org.jivesoftware.openfire.group.GroupManager;
import org.jivesoftware.openfire.privacy.PrivacyList;
......@@ -56,6 +37,14 @@ import org.xmpp.packet.IQ;
import org.xmpp.packet.JID;
import org.xmpp.packet.Presence;
import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
/**
* <p>A roster is a list of users that the user wishes to know if they are online.</p>
* <p>Rosters are similar to buddy groups in popular IM clients. The Roster class is
......@@ -82,17 +71,7 @@ public class Roster implements Cacheable, Externalizable {
*/
protected ConcurrentMap<String, Set<String>> implicitFrom = new ConcurrentHashMap<>();
private RosterItemProvider rosterItemProvider;
private String username;
private SessionManager sessionManager;
private XMPPServer server = XMPPServer.getInstance();
private RoutingTable routingTable;
private PresenceManager presenceManager;
/**
* Note: Used only for shared groups logic.
*/
private RosterManager rosterManager;
/**
* Constructor added for Externalizable. Do not use this constructor.
......@@ -115,10 +94,8 @@ public class Roster implements Cacheable, Externalizable {
* @param username The username of the user that owns this roster
*/
Roster(String username) {
presenceManager = XMPPServer.getInstance().getPresenceManager();
rosterManager = XMPPServer.getInstance().getRosterManager();
sessionManager = SessionManager.getInstance();
routingTable = XMPPServer.getInstance().getRoutingTable();
final RosterManager rosterManager = XMPPServer.getInstance().getRosterManager();
this.username = username;
// Get the shared groups of this user
......@@ -126,8 +103,7 @@ public class Roster implements Cacheable, Externalizable {
//Collection<Group> userGroups = GroupManager.getInstance().getGroups(getUserJID());
// Add RosterItems that belong to the personal roster
rosterItemProvider = RosterManager.getRosterItemProvider();
Iterator<RosterItem> items = rosterItemProvider.getItems(username);
Iterator<RosterItem> items = RosterManager.getRosterItemProvider().getItems(username);
while (items.hasNext()) {
RosterItem item = items.next();
// Check if the item (i.e. contact) belongs to a shared group of the user. Add the
......@@ -327,12 +303,12 @@ public class Roster implements Cacheable, Externalizable {
Collection<Group> groupsWithProp = GroupManager
.getInstance()
.search("sharedRoster.displayName", groupDisplayName);
Iterator<Group> itr = groupsWithProp.iterator();
while(itr.hasNext()) {
Group group = itr.next();
String showInRoster = group.getProperties().get("sharedRoster.showInRoster");
if(showInRoster != null && !showInRoster.equals("nobody")) {
throw new SharedGroupException("Cannot add an item to a shared group");
for ( Group group : groupsWithProp )
{
String showInRoster = group.getProperties().get( "sharedRoster.showInRoster" );
if ( showInRoster != null && !showInRoster.equals( "nobody" ) )
{
throw new SharedGroupException( "Cannot add an item to a shared group" );
}
}
}
......@@ -348,7 +324,7 @@ public class Roster implements Cacheable, Externalizable {
// Check if we need to make the new roster item persistent
if (persistent) {
rosterItem = rosterItemProvider.createItem(username, rosterItem);
rosterItem = RosterManager.getRosterItemProvider().createItem(username, rosterItem);
}
if (push) {
......@@ -403,16 +379,17 @@ public class Roster implements Cacheable, Externalizable {
}
}
try {
rosterItemProvider.createItem(username, item);
RosterManager.getRosterItemProvider().createItem(username, item);
} catch (UserAlreadyExistsException e) {
// Do nothing. We shouldn't be here.
Log.warn( "Unexpected error while updating roster item for user '{}'!", username, e);
}
} else {
// Item is not persistent and it does not belong to a shared contact so do nothing
}
} else {
// Update the backend data store
rosterItemProvider.updateItem(username, item);
RosterManager.getRosterItemProvider().updateItem(username, item);
}
// broadcast roster update
// Do not push items with a state of "None + Pending In"
......@@ -460,19 +437,19 @@ public class Roster implements Cacheable, Externalizable {
// Cancel any existing presence subscription between the user and the contact
if (subType == RosterItem.SUB_TO || subType == RosterItem.SUB_BOTH) {
Presence presence = new Presence();
presence.setFrom(server.createJID(username, null));
presence.setFrom(XMPPServer.getInstance().createJID(username, null));
presence.setTo(itemToRemove.getJid());
presence.setType(Presence.Type.unsubscribe);
server.getPacketRouter().route(presence);
XMPPServer.getInstance().getPacketRouter().route(presence);
}
// cancel any existing presence subscription between the contact and the user
if (subType == RosterItem.SUB_FROM || subType == RosterItem.SUB_BOTH) {
Presence presence = new Presence();
presence.setFrom(server.createJID(username, null));
presence.setFrom(XMPPServer.getInstance().createJID(username, null));
presence.setTo(itemToRemove.getJid());
presence.setType(Presence.Type.unsubscribed);
server.getPacketRouter().route(presence);
XMPPServer.getInstance().getPacketRouter().route(presence);
}
// If removing the user was successful, remove the user from the subscriber list:
......@@ -483,7 +460,7 @@ public class Roster implements Cacheable, Externalizable {
// belong to shared groups won't be persistent
if (item.getID() > 0) {
// If removing the user was successful, remove the user from the backend store
rosterItemProvider.deleteItem(username, item.getID());
RosterManager.getRosterItemProvider().deleteItem(username, item.getID());
}
// Broadcast the update to the user
......@@ -503,12 +480,12 @@ public class Roster implements Cacheable, Externalizable {
if (item != null) {
implicitFrom.remove(user.toBareJID());
// If the contact being removed is not a local user then ACK unsubscription
if (!server.isLocal(user)) {
if (!XMPPServer.getInstance().isLocal(user)) {
Presence presence = new Presence();
presence.setFrom(server.createJID(username, null));
presence.setFrom(XMPPServer.getInstance().createJID(username, null));
presence.setTo(user);
presence.setType(Presence.Type.unsubscribed);
server.getPacketRouter().route(presence);
XMPPServer.getInstance().getPacketRouter().route(presence);
}
// Fire event indicating that a roster item has been deleted
RosterEventDispatcher.contactDeleted(this, item);
......@@ -583,6 +560,8 @@ public class Roster implements Cacheable, Externalizable {
* @param packet The presence packet to broadcast
*/
public void broadcastPresence(Presence packet) {
final RoutingTable routingTable = XMPPServer.getInstance().getRoutingTable();
if (routingTable == null) {
return;
}
......@@ -592,7 +571,7 @@ public class Roster implements Cacheable, Externalizable {
if (from != null) {
// Try to use the active list of the session. If none was found then try to use
// the default privacy list of the session
ClientSession session = sessionManager.getSession(from);
ClientSession session = SessionManager.getInstance().getSession(from);
if (session != null) {
list = session.getActiveList();
list = list == null ? session.getDefaultList() : list;
......@@ -646,7 +625,7 @@ public class Roster implements Cacheable, Externalizable {
}
if (from != null) {
// Broadcast presence to other user's resources
sessionManager.broadcastPresenceToOtherResources(from, packet);
SessionManager.getInstance().broadcastPresenceToOtherResources(from, packet);
}
}
......@@ -658,6 +637,7 @@ public class Roster implements Cacheable, Externalizable {
* @return the list of users that belong ONLY to a shared group of this user.
*/
private Map<JID, List<Group>> getSharedUsers(Collection<Group> sharedGroups) {
final RosterManager rosterManager = XMPPServer.getInstance().getRosterManager();
// Get the users to process from the shared groups. Users that belong to different groups
// will have one entry in the map associated with all the groups
Map<JID, List<Group>> sharedGroupUsers = new HashMap<>();
......@@ -684,12 +664,15 @@ public class Roster implements Cacheable, Externalizable {
}
private void broadcast(org.xmpp.packet.Roster roster) {
JID recipient = server.createJID(username, null, true);
JID recipient = XMPPServer.getInstance().createJID(username, null, true);
roster.setTo(recipient);
if (sessionManager == null) {
sessionManager = SessionManager.getInstance();
// When roster versioning is enabled, the server MUST include
// the updated roster version with each roster push.
if (RosterManager.isRosterVersioningEnabled()) {
roster.getChildElement().addAttribute("ver", String.valueOf( roster.hashCode() ) );
}
sessionManager.userBroadcast(username, roster);
SessionManager.getInstance().userBroadcast(username, roster);
}
/**
......@@ -729,7 +712,8 @@ public class Roster implements Cacheable, Externalizable {
* Sends a presence probe to the probee for each connected resource of this user.
*/
private void probePresence(JID probee) {
for (ClientSession session : sessionManager.getSessions(username)) {
final PresenceManager presenceManager = XMPPServer.getInstance().getPresenceManager();
for (ClientSession session : SessionManager.getInstance().getSessions(username)) {
presenceManager.probePresence(session.getAddress(), probee);
}
}
......@@ -765,8 +749,8 @@ public class Roster implements Cacheable, Externalizable {
* @param addedUser the contact to update in the roster.
*/
void addSharedUser(Group group, JID addedUser) {
boolean newItem = false;
RosterItem item = null;
boolean newItem;
RosterItem item;
try {
// Get the RosterItem for the *local* user to add
item = getRosterItem(addedUser);
......@@ -789,6 +773,7 @@ public class Roster implements Cacheable, Externalizable {
Log.error("Group (" + group.getName() + ") includes non-existent username (" +
addedUser +
")");
return;
}
}
......@@ -806,6 +791,7 @@ public class Roster implements Cacheable, Externalizable {
sharedGroups.add(group);
// Set subscription type to BOTH if the roster user belongs to a shared group
// that is mutually visible with a shared group of the new roster item
final RosterManager rosterManager = XMPPServer.getInstance().getRosterManager();
if (rosterManager.hasMutualVisibility(getUsername(), userGroups, addedUser, sharedGroups)) {
item.setSubStatus(RosterItem.SUB_BOTH);
}
......@@ -873,8 +859,8 @@ public class Roster implements Cacheable, Externalizable {
* @param groups the groups where the contact is a member
*/
void addSharedUser(JID addedUser, Collection<Group> groups, Group addedGroup) {
boolean newItem = false;
RosterItem item = null;
boolean newItem;
RosterItem item;
try {
// Get the RosterItem for the *local* user to add
item = getRosterItem(addedUser);
......@@ -891,12 +877,14 @@ public class Roster implements Cacheable, Externalizable {
newItem = true;
} catch (UserNotFoundException ex) {
Log.error("Couldn't find a user with username (" + addedUser + ")");
return;
}
}
// Update the subscription of the item **based on the item groups**
Collection<Group> userGroups = GroupManager.getInstance().getGroups(getUserJID());
// Set subscription type to BOTH if the roster user belongs to a shared group
// that is mutually visible with a shared group of the new roster item
final RosterManager rosterManager = XMPPServer.getInstance().getRosterManager();
if (rosterManager.hasMutualVisibility(getUsername(), userGroups, addedUser, groups)) {
item.setSubStatus(RosterItem.SUB_BOTH);
for (Group group : groups) {
......@@ -1012,6 +1000,7 @@ public class Roster implements Cacheable, Externalizable {
sharedGroups.addAll(item.getSharedGroups());
// Set subscription type to BOTH if the roster user belongs to a shared group
// that is mutually visible with a shared group of the new roster item
final RosterManager rosterManager = XMPPServer.getInstance().getRosterManager();
if (rosterManager.hasMutualVisibility(getUsername(), userGroups, deletedUser,
sharedGroups)) {
item.setSubStatus(RosterItem.SUB_BOTH);
......@@ -1032,13 +1021,16 @@ public class Roster implements Cacheable, Externalizable {
}
} catch (SharedGroupException e) {
// Do nothing. Checkings are disabled so this exception should never happen.
Log.error( "Unexpected error while deleting user '{}' from shared group '{}'!", deletedUser, sharedGroup, e );
} catch (UserNotFoundException e) {
// Do nothing since the contact does not exist in the user's roster. (strange case!)
Log.warn( "Unexpected error while deleting user '{}' from shared group '{}'!", deletedUser, sharedGroup, e );
}
}
void deleteSharedUser(JID deletedUser, Group deletedGroup) {
try {
final RosterManager rosterManager = XMPPServer.getInstance().getRosterManager();
// Get the RosterItem for the *local* user to remove
RosterItem item = getRosterItem(deletedUser);
int groupSize = item.getSharedGroups().size() + item.getInvisibleSharedGroups().size();
......@@ -1098,8 +1090,10 @@ public class Roster implements Cacheable, Externalizable {
}
} catch (SharedGroupException e) {
// Do nothing. Checkings are disabled so this exception should never happen.
Log.error( "Unexpected error while deleting user '{}' from shared group '{}'!", deletedUser, deletedGroup, e);
} catch (UserNotFoundException e) {
// Do nothing since the contact does not exist in the user's roster. (strange case!)
Log.warn( "Unexpected error while deleting user '{}' from shared group '{}'!", deletedUser, deletedGroup, e);
}
}
......@@ -1119,10 +1113,11 @@ public class Roster implements Cacheable, Externalizable {
try {
// Get the RosterItem for the *local* user to add
item = getRosterItem(user);
// Brodcast to all the user resources of the updated roster item
// Broadcast to all the user resources of the updated roster item
broadcast(item, true);
} catch (UserNotFoundException e) {
// Do nothing since the contact does not exist in the user's roster. (strange case!)
Log.warn( "Unexpected error while broadcasting shared group rename for user '{}'!", user, e);
}
}
}
......@@ -1131,6 +1126,40 @@ public class Roster implements Cacheable, Externalizable {
return XMPPServer.getInstance().createJID(getUsername(), null, true);
}
@Override
public boolean equals( Object o )
{
if ( this == o )
{
return true;
}
if ( o == null || getClass() != o.getClass() )
{
return false;
}
final Roster roster = (Roster) o;
if ( !rosterItems.equals( roster.rosterItems ) )
{
return false;
}
if ( !implicitFrom.equals( roster.implicitFrom ) )
{
return false;
}
return username.equals( roster.username );
}
@Override
public int hashCode()
{
int result = rosterItems.hashCode();
result = 31 * result + implicitFrom.hashCode();
result = 31 * result + username.hashCode();
return result;
}
@Override
public void writeExternal(ObjectOutput out) throws IOException {
ExternalizableUtil.getInstance().writeSafeUTF(out, username);
......@@ -1140,12 +1169,6 @@ public class Roster implements Cacheable, Externalizable {
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
presenceManager = XMPPServer.getInstance().getPresenceManager();
rosterManager = XMPPServer.getInstance().getRosterManager();
sessionManager = SessionManager.getInstance();
rosterItemProvider = RosterManager.getRosterItemProvider();
routingTable = XMPPServer.getInstance().getRoutingTable();
username = ExternalizableUtil.getInstance().readSafeUTF(in);
ExternalizableUtil.getInstance().readExternalizableMap(in, rosterItems, getClass().getClassLoader());
ExternalizableUtil.getInstance().readStringsMap(in, implicitFrom);
......
......@@ -73,6 +73,15 @@ public class RosterManager extends BasicModule implements GroupEventListener, Us
return JiveGlobals.getBooleanProperty("xmpp.client.roster.active", true);
}
/**
* Returns true if the roster versioning is enabled.
*
* @return true if the roster versioning is enabled.
*/
public static boolean isRosterVersioningEnabled() {
return JiveGlobals.getBooleanProperty("xmpp.client.roster.versioning.active", true);
}
public RosterManager() {
super("Roster Manager");
rosterCache = CacheFactory.createCache("Roster");
......@@ -143,6 +152,7 @@ public class RosterManager extends BasicModule implements GroupEventListener, Us
}
catch (SharedGroupException e) {
// Do nothing. We shouldn't have this exception since we disabled the checkings
Log.warn( "Unexpected exception while deleting roster of user '{}' .", user, e );
}
}
// Remove the cached roster from memory
......@@ -160,9 +170,10 @@ public class RosterManager extends BasicModule implements GroupEventListener, Us
}
catch (SharedGroupException e) {
// Do nothing. We shouldn't have this exception since we disabled the checkings
Log.warn( "Unexpected exception while deleting roster of user '{}' .", user, e );
}
catch (UserNotFoundException e) {
// Do nothing.
// Deleted user had user that no longer exists on their roster. Ignore and move on.
}
}
}
......@@ -348,13 +359,16 @@ public class RosterManager extends BasicModule implements GroupEventListener, Us
// Iterate on all the affected users and update their rosters
for (JID updatedUser : users) {
// Get the roster to update.
Roster roster = null;
Roster roster;
if (server.isLocal(updatedUser)) {
roster = rosterCache.get(updatedUser.getNode());
}
if (roster != null) {
try {
roster = getRoster(updatedUser.getNode());
// Update the roster with the new group display name
roster.shareGroupRenamed(users);
} catch (UserNotFoundException e) {
Log.debug( "Unexpected exception while applying group modification for user '{}' .", updatedUser.getNode(), e );
}
}
}
}
......@@ -538,19 +552,17 @@ public class RosterManager extends BasicModule implements GroupEventListener, Us
// Get the roster to update
Roster roster = null;
if (server.isLocal(userToUpdate)) {
// Check that the user exists, if not then continue with the next user
// Get the roster. If the user does not exist then continue with the next user
try {
UserManager.getInstance().getUser(userToUpdate.getNode());
roster = getRoster(userToUpdate.getNode());
}
catch (UserNotFoundException e) {
continue;
}
roster = rosterCache.get(userToUpdate.getNode());
}
// Only update rosters in memory
if (roster != null) {
// Update roster
roster.addSharedUser(group, newUserJID);
}
if (!server.isLocal(userToUpdate)) {
// Susbcribe to the presence of the remote user. This is only necessary for
// remote users and may only work with remote users that **automatically**
......@@ -577,19 +589,17 @@ public class RosterManager extends BasicModule implements GroupEventListener, Us
// Get the roster to update
Roster roster = null;
if (server.isLocal(userToUpdate)) {
// Check that the user exists, if not then continue with the next user
// Get the roster. If the user does not exist then continue with the next user
try {
UserManager.getInstance().getUser(userToUpdate.getNode());
roster = getRoster(userToUpdate.getNode());
}
catch (UserNotFoundException e) {
continue;
}
roster = rosterCache.get(userToUpdate.getNode());
}
// Only update rosters in memory
if (roster != null) {
// Update roster
roster.deleteSharedUser(group, userJID);
}
if (!server.isLocal(userToUpdate)) {
// Unsusbcribe from the presence of the remote user. This is only necessary for
// remote users and may only work with remote users that **automatically**
......@@ -639,7 +649,11 @@ public class RosterManager extends BasicModule implements GroupEventListener, Us
// Get the roster of the added user.
Roster addedUserRoster = null;
if (server.isLocal(addedUser)) {
addedUserRoster = rosterCache.get(addedUser.getNode());
try {
addedUserRoster = getRoster(addedUser.getNode());
} catch (UserNotFoundException e) {
Log.warn( "Unexpected exception while adding user '{}' to group '{}'.", addedUser, group, e );
}
}
// Iterate on all the affected users and update their rosters
......@@ -648,23 +662,25 @@ public class RosterManager extends BasicModule implements GroupEventListener, Us
// Get the roster to update
Roster roster = null;
if (server.isLocal(userToUpdate)) {
// Check that the user exists, if not then continue with the next user
// Get the roster. If the user does not exist then continue with the next user
try {
UserManager.getInstance().getUser(userToUpdate.getNode());
roster = getRoster(userToUpdate.getNode());
}
catch (UserNotFoundException e) {
continue;
}
roster = rosterCache.get(userToUpdate.getNode());
}
// Only update rosters in memory
if (roster != null) {
// Update roster
roster.addSharedUser(group, addedUser);
}
// Check if the roster is still not in memory
if (addedUserRoster == null && server.isLocal(addedUser)) {
try {
addedUserRoster =
rosterCache.get(addedUser.getNode());
getRoster(addedUser.getNode());
} catch (UserNotFoundException e) {
Log.warn( "Unexpected exception while adding user '{}' to group '{}'.", addedUser, group, e );
}
}
// Update the roster of the newly added group user.
if (addedUserRoster != null) {
......@@ -708,7 +724,11 @@ public class RosterManager extends BasicModule implements GroupEventListener, Us
// Get the roster of the deleted user.
Roster deletedUserRoster = null;
if (server.isLocal(deletedUser)) {
deletedUserRoster = rosterCache.get(deletedUser.getNode());
try {
deletedUserRoster = getRoster(deletedUser.getNode());
} catch (UserNotFoundException e) {
Log.warn( "Unexpected exception while deleting user '{}' from group '{}'.", deletedUser, group, e );
}
}
// Iterate on all the affected users and update their rosters
......@@ -716,24 +736,17 @@ public class RosterManager extends BasicModule implements GroupEventListener, Us
// Get the roster to update
Roster roster = null;
if (server.isLocal(userToUpdate)) {
// Check that the user exists, if not then continue with the next user
// Get the roster. If the user does not exist then continue with the next user
try {
UserManager.getInstance().getUser(userToUpdate.getNode());
roster = getRoster(userToUpdate.getNode());
}
catch (UserNotFoundException e) {
continue;
}
roster = rosterCache.get(userToUpdate.getNode());
}
// Only update rosters in memory
if (roster != null) {
// Update roster
roster.deleteSharedUser(group, deletedUser);
}
// Check if the roster is still not in memory
if (deletedUserRoster == null && server.isLocal(deletedUser)) {
deletedUserRoster =
rosterCache.get(deletedUser.getNode());
}
// Update the roster of the newly deleted group user.
if (deletedUserRoster != null) {
deletedUserRoster.deleteSharedUser(userToUpdate, group);
......
......@@ -29,6 +29,7 @@ import org.jivesoftware.openfire.cluster.ClusterManager;
import org.jivesoftware.openfire.net.SASLAuthentication;
import org.jivesoftware.openfire.privacy.PrivacyList;
import org.jivesoftware.openfire.privacy.PrivacyListManager;
import org.jivesoftware.openfire.roster.RosterManager;
import org.jivesoftware.openfire.spi.ConnectionConfiguration;
import org.jivesoftware.openfire.streammanagement.StreamManager;
import org.jivesoftware.openfire.user.PresenceEventDispatcher;
......@@ -877,6 +878,12 @@ public class LocalClientSession extends LocalSession implements ClientSession {
"<compression xmlns=\"http://jabber.org/features/compress\"><method>zlib</method></compression>");
}
// If a server supports roster versioning,
// then it MUST advertise the following stream feature during stream negotiation.
if (RosterManager.isRosterVersioningEnabled()) {
sb.append("<ver xmlns=\"urn:xmpp:features:rosterver\"/>");
}
if (getAuthToken() == null) {
// Advertise that the server supports Non-SASL Authentication
if ( XMPPServer.getInstance().getIQRouter().supports( "jabber:iq:auth" ) ) {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment