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