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) {
returnPacket = cachedRoster.getReset();
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.setType(IQ.Type.result);
returnPacket.setTo(sender); returnPacket.setTo(sender);
returnPacket.setID(packet.getID()); returnPacket.setID(packet.getID());
...@@ -343,4 +359,4 @@ public class IQRosterHandler extends IQHandler implements ServerFeaturesProvider ...@@ -343,4 +359,4 @@ public class IQRosterHandler extends IQHandler implements ServerFeaturesProvider
public Iterator<String> getFeatures() { public Iterator<String> getFeatures() {
return Collections.singleton("jabber:iq:roster").iterator(); return Collections.singleton("jabber:iq:roster").iterator();
} }
} }
\ No newline at end of file
...@@ -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,14 +303,14 @@ public class Roster implements Cacheable, Externalizable { ...@@ -327,14 +303,14 @@ 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" );
} }
} }
} }
} }
org.xmpp.packet.Roster roster = new org.xmpp.packet.Roster(); org.xmpp.packet.Roster roster = new org.xmpp.packet.Roster();
...@@ -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);
......
/* /*
* Copyright (C) 2004-2008 Jive Software. All rights reserved. * Copyright (C) 2004-2008 Jive Software. All rights reserved.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.jivesoftware.openfire.roster; package org.jivesoftware.openfire.roster;
import org.jivesoftware.openfire.RoutingTable; import org.jivesoftware.openfire.RoutingTable;
import org.jivesoftware.openfire.SharedGroupException; import org.jivesoftware.openfire.SharedGroupException;
import org.jivesoftware.openfire.XMPPServer; import org.jivesoftware.openfire.XMPPServer;
import org.jivesoftware.openfire.container.BasicModule; import org.jivesoftware.openfire.container.BasicModule;
import org.jivesoftware.openfire.event.GroupEventDispatcher; import org.jivesoftware.openfire.event.GroupEventDispatcher;
import org.jivesoftware.openfire.event.GroupEventListener; import org.jivesoftware.openfire.event.GroupEventListener;
import org.jivesoftware.openfire.event.UserEventDispatcher; import org.jivesoftware.openfire.event.UserEventDispatcher;
import org.jivesoftware.openfire.event.UserEventListener; import org.jivesoftware.openfire.event.UserEventListener;
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.group.GroupNotFoundException; import org.jivesoftware.openfire.group.GroupNotFoundException;
import org.jivesoftware.openfire.user.User; import org.jivesoftware.openfire.user.User;
import org.jivesoftware.openfire.user.UserManager; import org.jivesoftware.openfire.user.UserManager;
import org.jivesoftware.openfire.user.UserNotFoundException; import org.jivesoftware.openfire.user.UserNotFoundException;
import org.jivesoftware.util.ClassUtils; import org.jivesoftware.util.ClassUtils;
import org.jivesoftware.util.JiveGlobals; import org.jivesoftware.util.JiveGlobals;
import org.jivesoftware.util.PropertyEventDispatcher; import org.jivesoftware.util.PropertyEventDispatcher;
import org.jivesoftware.util.PropertyEventListener; import org.jivesoftware.util.PropertyEventListener;
import org.jivesoftware.util.cache.Cache; import org.jivesoftware.util.cache.Cache;
import org.jivesoftware.util.cache.CacheFactory; import org.jivesoftware.util.cache.CacheFactory;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.xmpp.packet.JID; import org.xmpp.packet.JID;
import org.xmpp.packet.Presence; import org.xmpp.packet.Presence;
import java.util.*; import java.util.*;
/** /**
* A simple service that allows components to retrieve a roster based solely on the ID * A simple service that allows components to retrieve a roster based solely on the ID
* of the owner. Users have convenience methods for obtaining a roster associated with * of the owner. Users have convenience methods for obtaining a roster associated with
* the owner. However there are many components that need to retrieve the roster * the owner. However there are many components that need to retrieve the roster
* based solely on the generic ID owner key. This interface defines a service that can * based solely on the generic ID owner key. This interface defines a service that can
* do that. This allows classes that generically manage resource for resource owners * do that. This allows classes that generically manage resource for resource owners
* (such as presence updates) to generically offer their services without knowing or * (such as presence updates) to generically offer their services without knowing or
* caring if the roster owner is a user, chatbot, etc. * caring if the roster owner is a user, chatbot, etc.
* *
* @author Iain Shigeoka * @author Iain Shigeoka
*/ */
public class RosterManager extends BasicModule implements GroupEventListener, UserEventListener { public class RosterManager extends BasicModule implements GroupEventListener, UserEventListener {
private static final Logger Log = LoggerFactory.getLogger(RosterManager.class); private static final Logger Log = LoggerFactory.getLogger(RosterManager.class);
private Cache<String, Roster> rosterCache = null; private Cache<String, Roster> rosterCache = null;
private XMPPServer server; private XMPPServer server;
private RoutingTable routingTable; private RoutingTable routingTable;
private RosterItemProvider provider; private RosterItemProvider provider;
/** /**
* Returns true if the roster service is enabled. When disabled it is not possible to * Returns true if the roster service is enabled. When disabled it is not possible to
* retrieve users rosters or broadcast presence packets to roster contacts. * retrieve users rosters or broadcast presence packets to roster contacts.
* *
* @return true if the roster service is enabled. * @return true if the roster service is enabled.
*/ */
public static boolean isRosterServiceEnabled() { public static boolean isRosterServiceEnabled() {
return JiveGlobals.getBooleanProperty("xmpp.client.roster.active", true); return JiveGlobals.getBooleanProperty("xmpp.client.roster.active", true);
} }
public RosterManager() { /**
super("Roster Manager"); * Returns true if the roster versioning is enabled.
rosterCache = CacheFactory.createCache("Roster"); *
* @return true if the roster versioning is enabled.
initProvider(); */
public static boolean isRosterVersioningEnabled() {
PropertyEventDispatcher.addListener(new PropertyEventListener() { return JiveGlobals.getBooleanProperty("xmpp.client.roster.versioning.active", true);
@Override }
public void propertySet(String property, Map params) {
if (property.equals("provider.roster.className")) { public RosterManager() {
initProvider(); super("Roster Manager");
} rosterCache = CacheFactory.createCache("Roster");
}
@Override initProvider();
public void propertyDeleted(String property, Map params) {}
@Override PropertyEventDispatcher.addListener(new PropertyEventListener() {
public void xmlPropertySet(String property, Map params) {} @Override
@Override public void propertySet(String property, Map params) {
public void xmlPropertyDeleted(String property, Map params) {} if (property.equals("provider.roster.className")) {
}); initProvider();
}
} }
@Override
/** public void propertyDeleted(String property, Map params) {}
* Returns the roster for the given username. @Override
* public void xmlPropertySet(String property, Map params) {}
* @param username the username to search for. @Override
* @return the roster associated with the ID. public void xmlPropertyDeleted(String property, Map params) {}
* @throws org.jivesoftware.openfire.user.UserNotFoundException if the ID does not correspond });
* to a known entity on the server.
*/ }
public Roster getRoster(String username) throws UserNotFoundException {
Roster roster = rosterCache.get(username); /**
if (roster == null) { * Returns the roster for the given username.
// Synchronize using a unique key so that other threads loading the User *
// and not the Roster cannot produce a deadlock * @param username the username to search for.
synchronized ((username + " ro").intern()) { * @return the roster associated with the ID.
roster = rosterCache.get(username); * @throws org.jivesoftware.openfire.user.UserNotFoundException if the ID does not correspond
if (roster == null) { * to a known entity on the server.
// Not in cache so load a new one: */
roster = new Roster(username); public Roster getRoster(String username) throws UserNotFoundException {
rosterCache.put(username, roster); Roster roster = rosterCache.get(username);
} if (roster == null) {
} // Synchronize using a unique key so that other threads loading the User
} // and not the Roster cannot produce a deadlock
return roster; synchronized ((username + " ro").intern()) {
} roster = rosterCache.get(username);
if (roster == null) {
/** // Not in cache so load a new one:
* Removes the entire roster of a given user. This is necessary when a user roster = new Roster(username);
* account is being deleted from the server. rosterCache.put(username, roster);
* }
* @param user the user. }
*/ }
public void deleteRoster(JID user) { return roster;
if (!server.isLocal(user)) { }
// Ignore request if user is not a local user
return; /**
} * Removes the entire roster of a given user. This is necessary when a user
try { * account is being deleted from the server.
String username = user.getNode(); *
// Get the roster of the deleted user * @param user the user.
Roster roster = getRoster(username); */
// Remove each roster item from the user's roster public void deleteRoster(JID user) {
for (RosterItem item : roster.getRosterItems()) { if (!server.isLocal(user)) {
try { // Ignore request if user is not a local user
roster.deleteRosterItem(item.getJid(), false); return;
} }
catch (SharedGroupException e) { try {
// Do nothing. We shouldn't have this exception since we disabled the checkings String username = user.getNode();
} // Get the roster of the deleted user
} Roster roster = getRoster(username);
// Remove the cached roster from memory // Remove each roster item from the user's roster
rosterCache.remove(username); for (RosterItem item : roster.getRosterItems()) {
try {
// Get the rosters that have a reference to the deleted user roster.deleteRosterItem(item.getJid(), false);
Iterator<String> usernames = provider.getUsernames(user.toBareJID()); }
while (usernames.hasNext()) { catch (SharedGroupException e) {
username = usernames.next(); // Do nothing. We shouldn't have this exception since we disabled the checkings
try { Log.warn( "Unexpected exception while deleting roster of user '{}' .", user, e );
// Get the roster that has a reference to the deleted user }
roster = getRoster(username); }
// Remove the deleted user reference from this roster // Remove the cached roster from memory
roster.deleteRosterItem(user, false); rosterCache.remove(username);
}
catch (SharedGroupException e) { // Get the rosters that have a reference to the deleted user
// Do nothing. We shouldn't have this exception since we disabled the checkings Iterator<String> usernames = provider.getUsernames(user.toBareJID());
} while (usernames.hasNext()) {
catch (UserNotFoundException e) { username = usernames.next();
// Do nothing. try {
} // Get the roster that has a reference to the deleted user
} roster = getRoster(username);
} // Remove the deleted user reference from this roster
catch (UnsupportedOperationException | UserNotFoundException e) { roster.deleteRosterItem(user, false);
// Do nothing }
} 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 );
/** }
* Returns a collection with all the groups that the user may include in his roster. The catch (UserNotFoundException e) {
* following criteria will be used to select the groups: 1) Groups that are configured so that // Deleted user had user that no longer exists on their roster. Ignore and move on.
* everybody can include in his roster, 2) Groups that are configured so that its users may }
* include the group in their rosters and the user is a group user of the group and 3) User }
* belongs to a Group that may see a Group that whose members may include the Group in their }
* rosters. catch (UnsupportedOperationException | UserNotFoundException e) {
* // Do nothing
* @param username the username of the user to return his shared groups. }
* @return a collection with all the groups that the user may include in his roster. }
*/
public Collection<Group> getSharedGroups(String username) { /**
Collection<Group> answer = new HashSet<>(); * Returns a collection with all the groups that the user may include in his roster. The
Collection<Group> groups = GroupManager.getInstance().getSharedGroups(username); * following criteria will be used to select the groups: 1) Groups that are configured so that
for (Group group : groups) { * everybody can include in his roster, 2) Groups that are configured so that its users may
String showInRoster = group.getProperties().get("sharedRoster.showInRoster"); * include the group in their rosters and the user is a group user of the group and 3) User
if ("onlyGroup".equals(showInRoster)) { * belongs to a Group that may see a Group that whose members may include the Group in their
if (group.isUser(username)) { * rosters.
// The user belongs to the group so add the group to the answer *
answer.add(group); * @param username the username of the user to return his shared groups.
} * @return a collection with all the groups that the user may include in his roster.
else { */
// Check if the user belongs to a group that may see this group public Collection<Group> getSharedGroups(String username) {
Collection<Group> groupList = parseGroups(group.getProperties().get("sharedRoster.groupList")); Collection<Group> answer = new HashSet<>();
for (Group groupInList : groupList) { Collection<Group> groups = GroupManager.getInstance().getSharedGroups(username);
if (groupInList.isUser(username)) { for (Group group : groups) {
answer.add(group); String showInRoster = group.getProperties().get("sharedRoster.showInRoster");
} if ("onlyGroup".equals(showInRoster)) {
} if (group.isUser(username)) {
} // The user belongs to the group so add the group to the answer
} answer.add(group);
else if ("everybody".equals(showInRoster)) { }
// Anyone can see this group so add the group to the answer else {
answer.add(group); // Check if the user belongs to a group that may see this group
} Collection<Group> groupList = parseGroups(group.getProperties().get("sharedRoster.groupList"));
} for (Group groupInList : groupList) {
return answer; if (groupInList.isUser(username)) {
} answer.add(group);
}
/** }
* Returns the list of shared groups whose visibility is public. }
* }
* @return the list of shared groups whose visibility is public. else if ("everybody".equals(showInRoster)) {
*/ // Anyone can see this group so add the group to the answer
public Collection<Group> getPublicSharedGroups() { answer.add(group);
return GroupManager.getInstance().getPublicSharedGroups(); }
} }
return answer;
/** }
* Returns a collection of Groups obtained by parsing a comma delimited String with the name
* of groups. /**
* * Returns the list of shared groups whose visibility is public.
* @param groupNames a comma delimited string with group names. *
* @return a collection of Groups obtained by parsing a comma delimited String with the name * @return the list of shared groups whose visibility is public.
* of groups. */
*/ public Collection<Group> getPublicSharedGroups() {
private Collection<Group> parseGroups(String groupNames) { return GroupManager.getInstance().getPublicSharedGroups();
Collection<Group> answer = new HashSet<>(); }
for (String groupName : parseGroupNames(groupNames)) {
try { /**
answer.add(GroupManager.getInstance().getGroup(groupName)); * Returns a collection of Groups obtained by parsing a comma delimited String with the name
} * of groups.
catch (GroupNotFoundException e) { *
// Do nothing. Silently ignore the invalid reference to the group * @param groupNames a comma delimited string with group names.
} * @return a collection of Groups obtained by parsing a comma delimited String with the name
} * of groups.
return answer; */
} private Collection<Group> parseGroups(String groupNames) {
Collection<Group> answer = new HashSet<>();
/** for (String groupName : parseGroupNames(groupNames)) {
* Returns a collection of Groups obtained by parsing a comma delimited String with the name try {
* of groups. answer.add(GroupManager.getInstance().getGroup(groupName));
* }
* @param groupNames a comma delimited string with group names. catch (GroupNotFoundException e) {
* @return a collection of Groups obtained by parsing a comma delimited String with the name // Do nothing. Silently ignore the invalid reference to the group
* of groups. }
*/ }
private static Collection<String> parseGroupNames(String groupNames) { return answer;
Collection<String> answer = new HashSet<>(); }
if (groupNames != null) {
StringTokenizer tokenizer = new StringTokenizer(groupNames, ","); /**
while (tokenizer.hasMoreTokens()) { * Returns a collection of Groups obtained by parsing a comma delimited String with the name
answer.add(tokenizer.nextToken()); * of groups.
} *
} * @param groupNames a comma delimited string with group names.
return answer; * @return a collection of Groups obtained by parsing a comma delimited String with the name
} * of groups.
*/
@Override private static Collection<String> parseGroupNames(String groupNames) {
public void groupCreated(Group group, Map params) { Collection<String> answer = new HashSet<>();
//Do nothing if (groupNames != null) {
} StringTokenizer tokenizer = new StringTokenizer(groupNames, ",");
while (tokenizer.hasMoreTokens()) {
@Override answer.add(tokenizer.nextToken());
public void groupDeleting(Group group, Map params) { }
// Get group members }
Collection<JID> users = new HashSet<>(group.getMembers()); return answer;
users.addAll(group.getAdmins()); }
// Get users whose roster will be updated
Collection<JID> affectedUsers = getAffectedUsers(group); @Override
// Iterate on group members and update rosters of affected users public void groupCreated(Group group, Map params) {
for (JID deletedUser : users) { //Do nothing
groupUserDeleted(group, affectedUsers, deletedUser); }
}
} @Override
public void groupDeleting(Group group, Map params) {
@Override // Get group members
public void groupModified(Group group, Map params) { Collection<JID> users = new HashSet<>(group.getMembers());
// Do nothing if no group property has been modified users.addAll(group.getAdmins());
if ("propertyDeleted".equals(params.get("type"))) { // Get users whose roster will be updated
return; Collection<JID> affectedUsers = getAffectedUsers(group);
} // Iterate on group members and update rosters of affected users
String keyChanged = (String) params.get("propertyKey"); for (JID deletedUser : users) {
String originalValue = (String) params.get("originalValue"); groupUserDeleted(group, affectedUsers, deletedUser);
}
}
if ("sharedRoster.showInRoster".equals(keyChanged)) {
String currentValue = group.getProperties().get("sharedRoster.showInRoster"); @Override
// Nothing has changed so do nothing. public void groupModified(Group group, Map params) {
if (currentValue.equals(originalValue)) { // Do nothing if no group property has been modified
return; if ("propertyDeleted".equals(params.get("type"))) {
} return;
// Get the users of the group }
Collection<JID> users = new HashSet<>(group.getMembers()); String keyChanged = (String) params.get("propertyKey");
users.addAll(group.getAdmins()); String originalValue = (String) params.get("originalValue");
// Get the users whose roster will be affected
Collection<JID> affectedUsers = getAffectedUsers(group, originalValue,
group.getProperties().get("sharedRoster.groupList")); if ("sharedRoster.showInRoster".equals(keyChanged)) {
// Remove the group members from the affected rosters String currentValue = group.getProperties().get("sharedRoster.showInRoster");
for (JID deletedUser : users) { // Nothing has changed so do nothing.
groupUserDeleted(group, affectedUsers, deletedUser); if (currentValue.equals(originalValue)) {
} return;
}
// Simulate that the group users has been added to the group. This will cause to push // Get the users of the group
// roster items to the "affected" users for the group users Collection<JID> users = new HashSet<>(group.getMembers());
for (JID user : users) { users.addAll(group.getAdmins());
groupUserAdded(group, user); // Get the users whose roster will be affected
} Collection<JID> affectedUsers = getAffectedUsers(group, originalValue,
} group.getProperties().get("sharedRoster.groupList"));
else if ("sharedRoster.groupList".equals(keyChanged)) { // Remove the group members from the affected rosters
String currentValue = group.getProperties().get("sharedRoster.groupList"); for (JID deletedUser : users) {
// Nothing has changed so do nothing. groupUserDeleted(group, affectedUsers, deletedUser);
if (currentValue.equals(originalValue)) { }
return;
} // Simulate that the group users has been added to the group. This will cause to push
// Get the users of the group // roster items to the "affected" users for the group users
Collection<JID> users = new HashSet<>(group.getMembers()); for (JID user : users) {
users.addAll(group.getAdmins()); groupUserAdded(group, user);
// Get the users whose roster will be affected }
Collection<JID> affectedUsers = getAffectedUsers(group, }
group.getProperties().get("sharedRoster.showInRoster"), originalValue); else if ("sharedRoster.groupList".equals(keyChanged)) {
// Remove the group members from the affected rosters String currentValue = group.getProperties().get("sharedRoster.groupList");
for (JID deletedUser : users) { // Nothing has changed so do nothing.
groupUserDeleted(group, affectedUsers, deletedUser); if (currentValue.equals(originalValue)) {
} return;
}
// Simulate that the group users has been added to the group. This will cause to push // Get the users of the group
// roster items to the "affected" users for the group users Collection<JID> users = new HashSet<>(group.getMembers());
for (JID user : users) { users.addAll(group.getAdmins());
groupUserAdded(group, user); // Get the users whose roster will be affected
} Collection<JID> affectedUsers = getAffectedUsers(group,
} group.getProperties().get("sharedRoster.showInRoster"), originalValue);
else if ("sharedRoster.displayName".equals(keyChanged)) { // Remove the group members from the affected rosters
String currentValue = group.getProperties().get("sharedRoster.displayName"); for (JID deletedUser : users) {
// Nothing has changed so do nothing. groupUserDeleted(group, affectedUsers, deletedUser);
if (currentValue.equals(originalValue)) { }
return;
} // Simulate that the group users has been added to the group. This will cause to push
// Do nothing if the group is not being shown in users' rosters // roster items to the "affected" users for the group users
if (!isSharedGroup(group)) { for (JID user : users) {
return; groupUserAdded(group, user);
} }
// Get all the affected users }
Collection<JID> users = getAffectedUsers(group); else if ("sharedRoster.displayName".equals(keyChanged)) {
// Iterate on all the affected users and update their rosters String currentValue = group.getProperties().get("sharedRoster.displayName");
for (JID updatedUser : users) { // Nothing has changed so do nothing.
// Get the roster to update. if (currentValue.equals(originalValue)) {
Roster roster = null; return;
if (server.isLocal(updatedUser)) { }
roster = rosterCache.get(updatedUser.getNode()); // Do nothing if the group is not being shown in users' rosters
} if (!isSharedGroup(group)) {
if (roster != null) { return;
// Update the roster with the new group display name }
roster.shareGroupRenamed(users); // Get all the affected users
} Collection<JID> users = getAffectedUsers(group);
} // Iterate on all the affected users and update their rosters
} for (JID updatedUser : users) {
} // Get the roster to update.
Roster roster;
@Override if (server.isLocal(updatedUser)) {
public void initialize(XMPPServer server) { try {
super.initialize(server); roster = getRoster(updatedUser.getNode());
this.server = server;
this.routingTable = server.getRoutingTable(); // Update the roster with the new group display name
roster.shareGroupRenamed(users);
RosterEventDispatcher.addListener(new RosterEventListener() { } catch (UserNotFoundException e) {
@Override Log.debug( "Unexpected exception while applying group modification for user '{}' .", updatedUser.getNode(), e );
public void rosterLoaded(Roster roster) { }
// Do nothing }
} }
}
@Override }
public boolean addingContact(Roster roster, RosterItem item, boolean persistent) {
// Do nothing @Override
return true; public void initialize(XMPPServer server) {
} super.initialize(server);
this.server = server;
@Override this.routingTable = server.getRoutingTable();
public void contactAdded(Roster roster, RosterItem item) {
// Set object again in cache. This is done so that other cluster nodes RosterEventDispatcher.addListener(new RosterEventListener() {
// get refreshed with latest version of the object @Override
rosterCache.put(roster.getUsername(), roster); public void rosterLoaded(Roster roster) {
} // Do nothing
}
@Override
public void contactUpdated(Roster roster, RosterItem item) { @Override
// Set object again in cache. This is done so that other cluster nodes public boolean addingContact(Roster roster, RosterItem item, boolean persistent) {
// get refreshed with latest version of the object // Do nothing
rosterCache.put(roster.getUsername(), roster); return true;
} }
@Override @Override
public void contactDeleted(Roster roster, RosterItem item) { public void contactAdded(Roster roster, RosterItem item) {
// Set object again in cache. This is done so that other cluster nodes // Set object again in cache. This is done so that other cluster nodes
// get refreshed with latest version of the object // get refreshed with latest version of the object
rosterCache.put(roster.getUsername(), roster); rosterCache.put(roster.getUsername(), roster);
} }
});
} @Override
public void contactUpdated(Roster roster, RosterItem item) {
/** // Set object again in cache. This is done so that other cluster nodes
* Returns true if the specified Group may be included in a user roster. The decision is made // get refreshed with latest version of the object
* based on the group properties that are configurable through the Admin Console. rosterCache.put(roster.getUsername(), roster);
* }
* @param group the group to check if it may be considered a shared group.
* @return true if the specified Group may be included in a user roster. @Override
*/ public void contactDeleted(Roster roster, RosterItem item) {
public static boolean isSharedGroup(Group group) { // Set object again in cache. This is done so that other cluster nodes
String showInRoster = group.getProperties().get("sharedRoster.showInRoster"); // get refreshed with latest version of the object
if ("onlyGroup".equals(showInRoster) || "everybody".equals(showInRoster)) { rosterCache.put(roster.getUsername(), roster);
return true; }
} });
return false; }
}
/**
/** * Returns true if the specified Group may be included in a user roster. The decision is made
* Returns true if the specified Group may be seen by all users in the system. The decision * based on the group properties that are configurable through the Admin Console.
* is made based on the group properties that are configurable through the Admin Console. *
* * @param group the group to check if it may be considered a shared group.
* @param group the group to check if it may be seen by all users in the system. * @return true if the specified Group may be included in a user roster.
* @return true if the specified Group may be seen by all users in the system. */
*/ public static boolean isSharedGroup(Group group) {
public static boolean isPublicSharedGroup(Group group) { String showInRoster = group.getProperties().get("sharedRoster.showInRoster");
String showInRoster = group.getProperties().get("sharedRoster.showInRoster"); if ("onlyGroup".equals(showInRoster) || "everybody".equals(showInRoster)) {
if ("everybody".equals(showInRoster)) { return true;
return true; }
} return false;
return false; }
}
/**
@Override * Returns true if the specified Group may be seen by all users in the system. The decision
public void memberAdded(Group group, Map params) { * is made based on the group properties that are configurable through the Admin Console.
JID addedUser = new JID((String) params.get("member")); *
// Do nothing if the user was an admin that became a member * @param group the group to check if it may be seen by all users in the system.
if (group.getAdmins().contains(addedUser)) { * @return true if the specified Group may be seen by all users in the system.
return; */
} public static boolean isPublicSharedGroup(Group group) {
if (!isSharedGroup(group)) { String showInRoster = group.getProperties().get("sharedRoster.showInRoster");
for (Group visibleGroup : getVisibleGroups(group)) { if ("everybody".equals(showInRoster)) {
// Get the list of affected users return true;
Collection<JID> users = new HashSet<>(visibleGroup.getMembers()); }
users.addAll(visibleGroup.getAdmins()); return false;
groupUserAdded(visibleGroup, users, addedUser); }
}
} @Override
else { public void memberAdded(Group group, Map params) {
groupUserAdded(group, addedUser); JID addedUser = new JID((String) params.get("member"));
} // Do nothing if the user was an admin that became a member
} if (group.getAdmins().contains(addedUser)) {
return;
@Override }
public void memberRemoved(Group group, Map params) { if (!isSharedGroup(group)) {
String member = (String) params.get("member"); for (Group visibleGroup : getVisibleGroups(group)) {
if (member == null) { // Get the list of affected users
return; Collection<JID> users = new HashSet<>(visibleGroup.getMembers());
} users.addAll(visibleGroup.getAdmins());
JID deletedUser = new JID(member); groupUserAdded(visibleGroup, users, addedUser);
// Do nothing if the user is still an admin }
if (group.getAdmins().contains(deletedUser)) { }
return; else {
} groupUserAdded(group, addedUser);
if (!isSharedGroup(group)) { }
for (Group visibleGroup : getVisibleGroups(group)) { }
// Get the list of affected users
Collection<JID> users = new HashSet<>(visibleGroup.getMembers()); @Override
users.addAll(visibleGroup.getAdmins()); public void memberRemoved(Group group, Map params) {
groupUserDeleted(visibleGroup, users, deletedUser); String member = (String) params.get("member");
} if (member == null) {
} return;
else { }
groupUserDeleted(group, deletedUser); JID deletedUser = new JID(member);
} // Do nothing if the user is still an admin
} if (group.getAdmins().contains(deletedUser)) {
return;
@Override }
public void adminAdded(Group group, Map params) { if (!isSharedGroup(group)) {
JID addedUser = new JID((String) params.get("admin")); for (Group visibleGroup : getVisibleGroups(group)) {
// Do nothing if the user was a member that became an admin // Get the list of affected users
if (group.getMembers().contains(addedUser)) { Collection<JID> users = new HashSet<>(visibleGroup.getMembers());
return; users.addAll(visibleGroup.getAdmins());
} groupUserDeleted(visibleGroup, users, deletedUser);
if (!isSharedGroup(group)) { }
for (Group visibleGroup : getVisibleGroups(group)) { }
// Get the list of affected users else {
Collection<JID> users = new HashSet<>(visibleGroup.getMembers()); groupUserDeleted(group, deletedUser);
users.addAll(visibleGroup.getAdmins()); }
groupUserAdded(visibleGroup, users, addedUser); }
}
} @Override
else { public void adminAdded(Group group, Map params) {
groupUserAdded(group, addedUser); JID addedUser = new JID((String) params.get("admin"));
} // Do nothing if the user was a member that became an admin
} if (group.getMembers().contains(addedUser)) {
return;
@Override }
public void adminRemoved(Group group, Map params) { if (!isSharedGroup(group)) {
JID deletedUser = new JID((String) params.get("admin")); for (Group visibleGroup : getVisibleGroups(group)) {
// Do nothing if the user is still a member // Get the list of affected users
if (group.getMembers().contains(deletedUser)) { Collection<JID> users = new HashSet<>(visibleGroup.getMembers());
return; users.addAll(visibleGroup.getAdmins());
} groupUserAdded(visibleGroup, users, addedUser);
// Do nothing if the group is not being shown in group members' rosters }
if (!isSharedGroup(group)) { }
for (Group visibleGroup : getVisibleGroups(group)) { else {
// Get the list of affected users groupUserAdded(group, addedUser);
Collection<JID> users = new HashSet<>(visibleGroup.getMembers()); }
users.addAll(visibleGroup.getAdmins()); }
groupUserDeleted(visibleGroup, users, deletedUser);
} @Override
} public void adminRemoved(Group group, Map params) {
else { JID deletedUser = new JID((String) params.get("admin"));
groupUserDeleted(group, deletedUser); // Do nothing if the user is still a member
} if (group.getMembers().contains(deletedUser)) {
} return;
}
/** // Do nothing if the group is not being shown in group members' rosters
* A new user has been created so members of public shared groups need to have if (!isSharedGroup(group)) {
* their rosters updated. Members of public shared groups need to have a roster for (Group visibleGroup : getVisibleGroups(group)) {
* item with subscription FROM for the new user since the new user can see them. // Get the list of affected users
* Collection<JID> users = new HashSet<>(visibleGroup.getMembers());
* @param newUser the newly created user. users.addAll(visibleGroup.getAdmins());
* @param params event parameters. groupUserDeleted(visibleGroup, users, deletedUser);
*/ }
@Override }
public void userCreated(User newUser, Map<String,Object> params) { else {
JID newUserJID = server.createJID(newUser.getUsername(), null); groupUserDeleted(group, deletedUser);
// Shared public groups that are public should have a presence subscription }
// of type FROM for the new user }
for (Group group : getPublicSharedGroups()) {
// Get group members of public group /**
Collection<JID> users = new HashSet<>(group.getMembers()); * A new user has been created so members of public shared groups need to have
users.addAll(group.getAdmins()); * their rosters updated. Members of public shared groups need to have a roster
// Update the roster of each group member to include a subscription of type FROM * item with subscription FROM for the new user since the new user can see them.
for (JID userToUpdate : users) { *
// Get the roster to update * @param newUser the newly created user.
Roster roster = null; * @param params event parameters.
if (server.isLocal(userToUpdate)) { */
// Check that the user exists, if not then continue with the next user @Override
try { public void userCreated(User newUser, Map<String,Object> params) {
UserManager.getInstance().getUser(userToUpdate.getNode()); JID newUserJID = server.createJID(newUser.getUsername(), null);
} // Shared public groups that are public should have a presence subscription
catch (UserNotFoundException e) { // of type FROM for the new user
continue; for (Group group : getPublicSharedGroups()) {
} // Get group members of public group
roster = rosterCache.get(userToUpdate.getNode()); Collection<JID> users = new HashSet<>(group.getMembers());
} users.addAll(group.getAdmins());
// Only update rosters in memory // Update the roster of each group member to include a subscription of type FROM
if (roster != null) { for (JID userToUpdate : users) {
roster.addSharedUser(group, newUserJID); // Get the roster to update
} Roster roster = null;
if (!server.isLocal(userToUpdate)) { if (server.isLocal(userToUpdate)) {
// Susbcribe to the presence of the remote user. This is only necessary for // Get the roster. If the user does not exist then continue with the next user
// remote users and may only work with remote users that **automatically** try {
// accept presence subscription requests roster = getRoster(userToUpdate.getNode());
sendSubscribeRequest(newUserJID, userToUpdate, true); }
} catch (UserNotFoundException e) {
} continue;
} }
} }
// Update roster
@Override roster.addSharedUser(group, newUserJID);
public void userDeleting(User user, Map<String,Object> params) {
// Shared public groups that have a presence subscription of type FROM if (!server.isLocal(userToUpdate)) {
// for the deleted user should no longer have a reference to the deleted user // Susbcribe to the presence of the remote user. This is only necessary for
JID userJID = server.createJID(user.getUsername(), null); // remote users and may only work with remote users that **automatically**
// Shared public groups that are public should have a presence subscription // accept presence subscription requests
// of type FROM for the new user sendSubscribeRequest(newUserJID, userToUpdate, true);
for (Group group : getPublicSharedGroups()) { }
// Get group members of public group }
Collection<JID> users = new HashSet<>(group.getMembers()); }
users.addAll(group.getAdmins()); }
// Update the roster of each group member to include a subscription of type FROM
for (JID userToUpdate : users) { @Override
// Get the roster to update public void userDeleting(User user, Map<String,Object> params) {
Roster roster = null; // Shared public groups that have a presence subscription of type FROM
if (server.isLocal(userToUpdate)) { // for the deleted user should no longer have a reference to the deleted user
// Check that the user exists, if not then continue with the next user JID userJID = server.createJID(user.getUsername(), null);
try { // Shared public groups that are public should have a presence subscription
UserManager.getInstance().getUser(userToUpdate.getNode()); // of type FROM for the new user
} for (Group group : getPublicSharedGroups()) {
catch (UserNotFoundException e) { // Get group members of public group
continue; Collection<JID> users = new HashSet<>(group.getMembers());
} users.addAll(group.getAdmins());
roster = rosterCache.get(userToUpdate.getNode()); // Update the roster of each group member to include a subscription of type FROM
} for (JID userToUpdate : users) {
// Only update rosters in memory // Get the roster to update
if (roster != null) { Roster roster = null;
roster.deleteSharedUser(group, userJID); if (server.isLocal(userToUpdate)) {
} // Get the roster. If the user does not exist then continue with the next user
if (!server.isLocal(userToUpdate)) { try {
// Unsusbcribe from the presence of the remote user. This is only necessary for roster = getRoster(userToUpdate.getNode());
// remote users and may only work with remote users that **automatically** }
// accept presence subscription requests catch (UserNotFoundException e) {
sendSubscribeRequest(userJID, userToUpdate, false); continue;
} }
} }
} // Update roster
roster.deleteSharedUser(group, userJID);
deleteRoster(userJID);
} if (!server.isLocal(userToUpdate)) {
// Unsusbcribe from the presence of the remote user. This is only necessary for
@Override // remote users and may only work with remote users that **automatically**
public void userModified(User user, Map<String,Object> params) { // accept presence subscription requests
if ("nameModified".equals(params.get("type"))) { sendSubscribeRequest(userJID, userToUpdate, false);
}
for (Group group : getSharedGroups(user.getUsername())) { }
ArrayList<JID> groupUsers = new ArrayList<>(); }
groupUsers.addAll(group.getAdmins());
groupUsers.addAll(group.getMembers()); deleteRoster(userJID);
}
for (JID groupUser : groupUsers) {
rosterCache.remove(groupUser.getNode()); @Override
} public void userModified(User user, Map<String,Object> params) {
} if ("nameModified".equals(params.get("type"))) {
}
} for (Group group : getSharedGroups(user.getUsername())) {
ArrayList<JID> groupUsers = new ArrayList<>();
/** groupUsers.addAll(group.getAdmins());
* Notification that a Group user has been added. Update the group users' roster accordingly. groupUsers.addAll(group.getMembers());
*
* @param group the group where the user was added. for (JID groupUser : groupUsers) {
* @param addedUser the username of the user that has been added to the group. rosterCache.remove(groupUser.getNode());
*/ }
private void groupUserAdded(Group group, JID addedUser) { }
groupUserAdded(group, getAffectedUsers(group), addedUser); }
} }
/** /**
* Notification that a Group user has been added. Update the group users' roster accordingly. * Notification that a Group user has been added. Update the group users' roster accordingly.
* *
* @param group the group where the user was added. * @param group the group where the user was added.
* @param users the users to update their rosters * @param addedUser the username of the user that has been added to the group.
* @param addedUser the username of the user that has been added to the group. */
*/ private void groupUserAdded(Group group, JID addedUser) {
private void groupUserAdded(Group group, Collection<JID> users, JID addedUser) { groupUserAdded(group, getAffectedUsers(group), addedUser);
// Get the roster of the added user. }
Roster addedUserRoster = null;
if (server.isLocal(addedUser)) { /**
addedUserRoster = rosterCache.get(addedUser.getNode()); * Notification that a Group user has been added. Update the group users' roster accordingly.
} *
* @param group the group where the user was added.
// Iterate on all the affected users and update their rosters * @param users the users to update their rosters
for (JID userToUpdate : users) { * @param addedUser the username of the user that has been added to the group.
if (!addedUser.equals(userToUpdate)) { */
// Get the roster to update private void groupUserAdded(Group group, Collection<JID> users, JID addedUser) {
Roster roster = null; // Get the roster of the added user.
if (server.isLocal(userToUpdate)) { Roster addedUserRoster = null;
// Check that the user exists, if not then continue with the next user if (server.isLocal(addedUser)) {
try { try {
UserManager.getInstance().getUser(userToUpdate.getNode()); addedUserRoster = getRoster(addedUser.getNode());
} } catch (UserNotFoundException e) {
catch (UserNotFoundException e) { Log.warn( "Unexpected exception while adding user '{}' to group '{}'.", addedUser, group, e );
continue; }
} }
roster = rosterCache.get(userToUpdate.getNode());
} // Iterate on all the affected users and update their rosters
// Only update rosters in memory for (JID userToUpdate : users) {
if (roster != null) { if (!addedUser.equals(userToUpdate)) {
roster.addSharedUser(group, addedUser); // Get the roster to update
} Roster roster = null;
// Check if the roster is still not in memory if (server.isLocal(userToUpdate)) {
if (addedUserRoster == null && server.isLocal(addedUser)) { // Get the roster. If the user does not exist then continue with the next user
addedUserRoster = try {
rosterCache.get(addedUser.getNode()); roster = getRoster(userToUpdate.getNode());
} }
// Update the roster of the newly added group user. catch (UserNotFoundException e) {
if (addedUserRoster != null) { continue;
Collection<Group> groups = GroupManager.getInstance().getGroups(userToUpdate); }
addedUserRoster.addSharedUser(userToUpdate, groups, group); }
} // Update roster
if (!server.isLocal(addedUser)) { roster.addSharedUser(group, addedUser);
// Susbcribe to the presence of the remote user. This is only necessary for
// remote users and may only work with remote users that **automatically** // Check if the roster is still not in memory
// accept presence subscription requests if (addedUserRoster == null && server.isLocal(addedUser)) {
sendSubscribeRequest(userToUpdate, addedUser, true); try {
} addedUserRoster =
if (!server.isLocal(userToUpdate)) { getRoster(addedUser.getNode());
// Susbcribe to the presence of the remote user. This is only necessary for } catch (UserNotFoundException e) {
// remote users and may only work with remote users that **automatically** Log.warn( "Unexpected exception while adding user '{}' to group '{}'.", addedUser, group, e );
// accept presence subscription requests }
sendSubscribeRequest(addedUser, userToUpdate, true); }
} // Update the roster of the newly added group user.
} if (addedUserRoster != null) {
} Collection<Group> groups = GroupManager.getInstance().getGroups(userToUpdate);
} addedUserRoster.addSharedUser(userToUpdate, groups, group);
}
/** if (!server.isLocal(addedUser)) {
* Notification that a Group user has been deleted. Update the group users' roster accordingly. // Susbcribe to the presence of the remote user. This is only necessary for
* // remote users and may only work with remote users that **automatically**
* @param group the group from where the user was deleted. // accept presence subscription requests
* @param deletedUser the username of the user that has been deleted from the group. sendSubscribeRequest(userToUpdate, addedUser, true);
*/ }
private void groupUserDeleted(Group group, JID deletedUser) { if (!server.isLocal(userToUpdate)) {
groupUserDeleted(group, getAffectedUsers(group), deletedUser); // Susbcribe to the presence of the remote user. This is only necessary for
} // remote users and may only work with remote users that **automatically**
// accept presence subscription requests
/** sendSubscribeRequest(addedUser, userToUpdate, true);
* Notification that a Group user has been deleted. Update the group users' roster accordingly. }
* }
* @param group the group from where the user was deleted. }
* @param users the users to update their rosters }
* @param deletedUser the username of the user that has been deleted from the group.
*/ /**
private void groupUserDeleted(Group group, Collection<JID> users, JID deletedUser) { * Notification that a Group user has been deleted. Update the group users' roster accordingly.
// Get the roster of the deleted user. *
Roster deletedUserRoster = null; * @param group the group from where the user was deleted.
if (server.isLocal(deletedUser)) { * @param deletedUser the username of the user that has been deleted from the group.
deletedUserRoster = rosterCache.get(deletedUser.getNode()); */
} private void groupUserDeleted(Group group, JID deletedUser) {
groupUserDeleted(group, getAffectedUsers(group), deletedUser);
// Iterate on all the affected users and update their rosters }
for (JID userToUpdate : users) {
// Get the roster to update /**
Roster roster = null; * Notification that a Group user has been deleted. Update the group users' roster accordingly.
if (server.isLocal(userToUpdate)) { *
// Check that the user exists, if not then continue with the next user * @param group the group from where the user was deleted.
try { * @param users the users to update their rosters
UserManager.getInstance().getUser(userToUpdate.getNode()); * @param deletedUser the username of the user that has been deleted from the group.
} */
catch (UserNotFoundException e) { private void groupUserDeleted(Group group, Collection<JID> users, JID deletedUser) {
continue; // Get the roster of the deleted user.
} Roster deletedUserRoster = null;
roster = rosterCache.get(userToUpdate.getNode()); if (server.isLocal(deletedUser)) {
} try {
// Only update rosters in memory deletedUserRoster = getRoster(deletedUser.getNode());
if (roster != null) { } catch (UserNotFoundException e) {
roster.deleteSharedUser(group, deletedUser); Log.warn( "Unexpected exception while deleting user '{}' from group '{}'.", deletedUser, group, e );
} }
// Check if the roster is still not in memory }
if (deletedUserRoster == null && server.isLocal(deletedUser)) {
deletedUserRoster = // Iterate on all the affected users and update their rosters
rosterCache.get(deletedUser.getNode()); for (JID userToUpdate : users) {
} // Get the roster to update
// Update the roster of the newly deleted group user. Roster roster = null;
if (deletedUserRoster != null) { if (server.isLocal(userToUpdate)) {
deletedUserRoster.deleteSharedUser(userToUpdate, group); // Get the roster. If the user does not exist then continue with the next user
} try {
if (!server.isLocal(deletedUser)) { roster = getRoster(userToUpdate.getNode());
// Unsusbcribe from the presence of the remote user. This is only necessary for }
// remote users and may only work with remote users that **automatically** catch (UserNotFoundException e) {
// accept presence subscription requests continue;
sendSubscribeRequest(userToUpdate, deletedUser, false); }
} }
if (!server.isLocal(userToUpdate)) { // Update roster
// Unsusbcribe from the presence of the remote user. This is only necessary for roster.deleteSharedUser(group, deletedUser);
// remote users and may only work with remote users that **automatically**
// accept presence subscription requests // Update the roster of the newly deleted group user.
sendSubscribeRequest(deletedUser, userToUpdate, false); if (deletedUserRoster != null) {
} deletedUserRoster.deleteSharedUser(userToUpdate, group);
} }
} if (!server.isLocal(deletedUser)) {
// Unsusbcribe from the presence of the remote user. This is only necessary for
private void sendSubscribeRequest(JID sender, JID recipient, boolean isSubscribe) { // remote users and may only work with remote users that **automatically**
Presence presence = new Presence(); // accept presence subscription requests
presence.setFrom(sender); sendSubscribeRequest(userToUpdate, deletedUser, false);
presence.setTo(recipient); }
if (isSubscribe) { if (!server.isLocal(userToUpdate)) {
presence.setType(Presence.Type.subscribe); // Unsusbcribe from the presence of the remote user. This is only necessary for
} // remote users and may only work with remote users that **automatically**
else { // accept presence subscription requests
presence.setType(Presence.Type.unsubscribe); sendSubscribeRequest(deletedUser, userToUpdate, false);
} }
routingTable.routePacket(recipient, presence, false); }
} }
private Collection<Group> getVisibleGroups(Group groupToCheck) { private void sendSubscribeRequest(JID sender, JID recipient, boolean isSubscribe) {
return GroupManager.getInstance().getVisibleGroups(groupToCheck); Presence presence = new Presence();
} presence.setFrom(sender);
presence.setTo(recipient);
/** if (isSubscribe) {
* Returns true if a given group is visible to a given user. That means, if the user can presence.setType(Presence.Type.subscribe);
* see the group in his roster. }
* else {
* @param group the group to check if the user can see. presence.setType(Presence.Type.unsubscribe);
* @param user the JID of the user to check if he may see the group. }
* @return true if a given group is visible to a given user. routingTable.routePacket(recipient, presence, false);
*/ }
public boolean isGroupVisible(Group group, JID user) {
String showInRoster = group.getProperties().get("sharedRoster.showInRoster"); private Collection<Group> getVisibleGroups(Group groupToCheck) {
if ("everybody".equals(showInRoster)) { return GroupManager.getInstance().getVisibleGroups(groupToCheck);
return true; }
}
else if ("onlyGroup".equals(showInRoster)) { /**
if (group.isUser(user)) { * Returns true if a given group is visible to a given user. That means, if the user can
return true; * see the group in his roster.
} *
// Check if the user belongs to a group that may see this group * @param group the group to check if the user can see.
Collection<Group> groupList = parseGroups(group.getProperties().get( * @param user the JID of the user to check if he may see the group.
"sharedRoster.groupList")); * @return true if a given group is visible to a given user.
for (Group groupInList : groupList) { */
if (groupInList.isUser(user)) { public boolean isGroupVisible(Group group, JID user) {
return true; String showInRoster = group.getProperties().get("sharedRoster.showInRoster");
} if ("everybody".equals(showInRoster)) {
} return true;
} }
return false; else if ("onlyGroup".equals(showInRoster)) {
} if (group.isUser(user)) {
return true;
/** }
* Returns all the users that are related to a shared group. This is the logic that we are // Check if the user belongs to a group that may see this group
* using: 1) If the group visiblity is configured as "Everybody" then all users in the system or Collection<Group> groupList = parseGroups(group.getProperties().get(
* all logged users in the system will be returned (configurable thorugh the "filterOffline" "sharedRoster.groupList"));
* flag), 2) if the group visiblity is configured as "onlyGroup" then all the group users will for (Group groupInList : groupList) {
* be included in the answer and 3) if the group visiblity is configured as "onlyGroup" and if (groupInList.isUser(user)) {
* the group allows other groups to include the group in the groups users' roster then all return true;
* the users of the allowed groups will be included in the answer. }
*/ }
private Collection<JID> getAffectedUsers(Group group) { }
return getAffectedUsers(group, group.getProperties().get("sharedRoster.showInRoster"), return false;
group.getProperties().get("sharedRoster.groupList")); }
}
/**
/** * Returns all the users that are related to a shared group. This is the logic that we are
* This method is similar to {@link #getAffectedUsers(Group)} except that it receives * using: 1) If the group visiblity is configured as "Everybody" then all users in the system or
* some group properties. The group properties are passed as parameters since the called of this * all logged users in the system will be returned (configurable thorugh the "filterOffline"
* method may want to obtain the related users of the group based in some properties values. * flag), 2) if the group visiblity is configured as "onlyGroup" then all the group users will
* * be included in the answer and 3) if the group visiblity is configured as "onlyGroup" and
* This is useful when the group is being edited and some properties has changed and we need to * the group allows other groups to include the group in the groups users' roster then all
* obtain the related users of the group based on the previous group state. * the users of the allowed groups will be included in the answer.
*/ */
private Collection<JID> getAffectedUsers(Group group, String showInRoster, String groupNames) { private Collection<JID> getAffectedUsers(Group group) {
// Answer an empty collection if the group is not being shown in users' rosters return getAffectedUsers(group, group.getProperties().get("sharedRoster.showInRoster"),
if (!"onlyGroup".equals(showInRoster) && !"everybody".equals(showInRoster)) { group.getProperties().get("sharedRoster.groupList"));
return new ArrayList<>(); }
}
// Add the users of the group /**
Collection<JID> users = new HashSet<>(group.getMembers()); * This method is similar to {@link #getAffectedUsers(Group)} except that it receives
users.addAll(group.getAdmins()); * some group properties. The group properties are passed as parameters since the called of this
// Check if anyone can see this shared group * method may want to obtain the related users of the group based in some properties values.
if ("everybody".equals(showInRoster)) { *
// Add all users in the system * This is useful when the group is being edited and some properties has changed and we need to
for (String username : UserManager.getInstance().getUsernames()) { * obtain the related users of the group based on the previous group state.
users.add(server.createJID(username, null, true)); */
} private Collection<JID> getAffectedUsers(Group group, String showInRoster, String groupNames) {
// Add all logged users. We don't need to add all users in the system since only the // Answer an empty collection if the group is not being shown in users' rosters
// logged ones will be affected. if (!"onlyGroup".equals(showInRoster) && !"everybody".equals(showInRoster)) {
//users.addAll(SessionManager.getInstance().getSessionUsers()); return new ArrayList<>();
} }
else { // Add the users of the group
// Add the users that may see the group Collection<JID> users = new HashSet<>(group.getMembers());
Collection<Group> groupList = parseGroups(groupNames); users.addAll(group.getAdmins());
for (Group groupInList : groupList) { // Check if anyone can see this shared group
users.addAll(groupInList.getMembers()); if ("everybody".equals(showInRoster)) {
users.addAll(groupInList.getAdmins()); // Add all users in the system
} for (String username : UserManager.getInstance().getUsernames()) {
} users.add(server.createJID(username, null, true));
return users; }
} // Add all logged users. We don't need to add all users in the system since only the
// logged ones will be affected.
Collection<JID> getSharedUsersForRoster(Group group, Roster roster) { //users.addAll(SessionManager.getInstance().getSessionUsers());
String showInRoster = group.getProperties().get("sharedRoster.showInRoster"); }
String groupNames = group.getProperties().get("sharedRoster.groupList"); else {
// Add the users that may see the group
// Answer an empty collection if the group is not being shown in users' rosters Collection<Group> groupList = parseGroups(groupNames);
if (!"onlyGroup".equals(showInRoster) && !"everybody".equals(showInRoster)) { for (Group groupInList : groupList) {
return new ArrayList<>(); users.addAll(groupInList.getMembers());
} users.addAll(groupInList.getAdmins());
}
// Add the users of the group }
Collection<JID> users = new HashSet<>(group.getMembers()); return users;
users.addAll(group.getAdmins()); }
// If the user of the roster belongs to the shared group then we should return Collection<JID> getSharedUsersForRoster(Group group, Roster roster) {
// users that need to be in the roster with subscription "from" String showInRoster = group.getProperties().get("sharedRoster.showInRoster");
if (group.isUser(roster.getUsername())) { String groupNames = group.getProperties().get("sharedRoster.groupList");
// Check if anyone can see this shared group
if ("everybody".equals(showInRoster)) { // Answer an empty collection if the group is not being shown in users' rosters
// Add all users in the system if (!"onlyGroup".equals(showInRoster) && !"everybody".equals(showInRoster)) {
for (String username : UserManager.getInstance().getUsernames()) { return new ArrayList<>();
users.add(server.createJID(username, null, true)); }
}
} // Add the users of the group
else { Collection<JID> users = new HashSet<>(group.getMembers());
// Add the users that may see the group users.addAll(group.getAdmins());
Collection<Group> groupList = parseGroups(groupNames);
for (Group groupInList : groupList) { // If the user of the roster belongs to the shared group then we should return
users.addAll(groupInList.getMembers()); // users that need to be in the roster with subscription "from"
users.addAll(groupInList.getAdmins()); if (group.isUser(roster.getUsername())) {
} // Check if anyone can see this shared group
} if ("everybody".equals(showInRoster)) {
} // Add all users in the system
return users; for (String username : UserManager.getInstance().getUsernames()) {
} users.add(server.createJID(username, null, true));
}
/** }
* Returns true if a group in the first collection may mutually see a group of the else {
* second collection. More precisely, return true if both collections contain a public // Add the users that may see the group
* group (i.e. anybody can see the group) or if both collection have a group that may see Collection<Group> groupList = parseGroups(groupNames);
* each other and the users are members of those groups or if one group is public and the for (Group groupInList : groupList) {
* other group allowed the public group to see it. users.addAll(groupInList.getMembers());
* users.addAll(groupInList.getAdmins());
* @param user the name of the user associated to the first collection of groups. This is always a local user. }
* @param groups a collection of groups to check against the other collection of groups. }
* @param otherUser the JID of the user associated to the second collection of groups. }
* @param otherGroups the other collection of groups to check against the first collection. return users;
* @return true if a group in the first collection may mutually see a group of the }
* second collection.
*/ /**
boolean hasMutualVisibility(String user, Collection<Group> groups, JID otherUser, * Returns true if a group in the first collection may mutually see a group of the
Collection<Group> otherGroups) { * second collection. More precisely, return true if both collections contain a public
for (Group group : groups) { * group (i.e. anybody can see the group) or if both collection have a group that may see
for (Group otherGroup : otherGroups) { * each other and the users are members of those groups or if one group is public and the
// Skip this groups if the users are not group users of the groups * other group allowed the public group to see it.
if (!group.isUser(user) || !otherGroup.isUser(otherUser)) { *
continue; * @param user the name of the user associated to the first collection of groups. This is always a local user.
} * @param groups a collection of groups to check against the other collection of groups.
if (group.equals(otherGroup)) { * @param otherUser the JID of the user associated to the second collection of groups.
return true; * @param otherGroups the other collection of groups to check against the first collection.
} * @return true if a group in the first collection may mutually see a group of the
String showInRoster = group.getProperties().get("sharedRoster.showInRoster"); * second collection.
String otherShowInRoster = otherGroup.getProperties().get("sharedRoster.showInRoster"); */
// Return true if both groups are public groups (i.e. anybody can see them) boolean hasMutualVisibility(String user, Collection<Group> groups, JID otherUser,
if ("everybody".equals(showInRoster) && "everybody".equals(otherShowInRoster)) { Collection<Group> otherGroups) {
return true; for (Group group : groups) {
} for (Group otherGroup : otherGroups) {
else if ("onlyGroup".equals(showInRoster) && "onlyGroup".equals(otherShowInRoster)) { // Skip this groups if the users are not group users of the groups
String groupNames = group.getProperties().get("sharedRoster.groupList"); if (!group.isUser(user) || !otherGroup.isUser(otherUser)) {
String otherGroupNames = otherGroup.getProperties().get("sharedRoster.groupList"); continue;
// Return true if each group may see the other group }
if (groupNames != null && otherGroupNames != null) { if (group.equals(otherGroup)) {
if (groupNames.contains(otherGroup.getName()) && return true;
otherGroupNames.contains(group.getName())) { }
return true; String showInRoster = group.getProperties().get("sharedRoster.showInRoster");
} String otherShowInRoster = otherGroup.getProperties().get("sharedRoster.showInRoster");
// Check if each shared group can be seen by a group where each user belongs // Return true if both groups are public groups (i.e. anybody can see them)
Collection<Group> groupList = parseGroups(groupNames); if ("everybody".equals(showInRoster) && "everybody".equals(otherShowInRoster)) {
Collection<Group> otherGroupList = parseGroups(otherGroupNames); return true;
for (Group groupName : groupList) { }
if (groupName.isUser(otherUser)) { else if ("onlyGroup".equals(showInRoster) && "onlyGroup".equals(otherShowInRoster)) {
for (Group otherGroupName : otherGroupList) { String groupNames = group.getProperties().get("sharedRoster.groupList");
if (otherGroupName.isUser(user)) { String otherGroupNames = otherGroup.getProperties().get("sharedRoster.groupList");
return true; // Return true if each group may see the other group
} if (groupNames != null && otherGroupNames != null) {
} if (groupNames.contains(otherGroup.getName()) &&
} otherGroupNames.contains(group.getName())) {
} return true;
} }
} // Check if each shared group can be seen by a group where each user belongs
else if ("everybody".equals(showInRoster) && "onlyGroup".equals(otherShowInRoster)) { Collection<Group> groupList = parseGroups(groupNames);
// Return true if one group is public and the other group allowed the public Collection<Group> otherGroupList = parseGroups(otherGroupNames);
// group to see him for (Group groupName : groupList) {
String otherGroupNames = otherGroup.getProperties().get("sharedRoster.groupList"); if (groupName.isUser(otherUser)) {
if (otherGroupNames != null && otherGroupNames.contains(group.getName())) { for (Group otherGroupName : otherGroupList) {
return true; if (otherGroupName.isUser(user)) {
} return true;
} }
else if ("onlyGroup".equals(showInRoster) && "everybody".equals(otherShowInRoster)) { }
// Return true if one group is public and the other group allowed the public }
// group to see him }
String groupNames = group.getProperties().get("sharedRoster.groupList"); }
// Return true if each group may see the other group }
if (groupNames != null && groupNames.contains(otherGroup.getName())) { else if ("everybody".equals(showInRoster) && "onlyGroup".equals(otherShowInRoster)) {
return true; // Return true if one group is public and the other group allowed the public
} // group to see him
} String otherGroupNames = otherGroup.getProperties().get("sharedRoster.groupList");
} if (otherGroupNames != null && otherGroupNames.contains(group.getName())) {
} return true;
return false; }
} }
else if ("onlyGroup".equals(showInRoster) && "everybody".equals(otherShowInRoster)) {
@Override // Return true if one group is public and the other group allowed the public
public void start() throws IllegalStateException { // group to see him
super.start(); String groupNames = group.getProperties().get("sharedRoster.groupList");
// Add this module as a user event listener so we can update // Return true if each group may see the other group
// rosters when users are created or deleted if (groupNames != null && groupNames.contains(otherGroup.getName())) {
UserEventDispatcher.addListener(this); return true;
// Add the new instance as a listener of group events }
GroupEventDispatcher.addListener(this); }
} }
}
@Override return false;
public void stop() { }
super.stop();
// Remove this module as a user event listener @Override
UserEventDispatcher.removeListener(this); public void start() throws IllegalStateException {
// Remove this module as a listener of group events super.start();
GroupEventDispatcher.removeListener(this); // Add this module as a user event listener so we can update
} // rosters when users are created or deleted
UserEventDispatcher.addListener(this);
public static RosterItemProvider getRosterItemProvider() { // Add the new instance as a listener of group events
return XMPPServer.getInstance().getRosterManager().provider; GroupEventDispatcher.addListener(this);
} }
private void initProvider() { @Override
JiveGlobals.migrateProperty("provider.roster.className"); public void stop() {
String className = JiveGlobals.getProperty("provider.roster.className", super.stop();
"org.jivesoftware.openfire.roster.DefaultRosterItemProvider"); // Remove this module as a user event listener
UserEventDispatcher.removeListener(this);
if (provider == null || !className.equals(provider.getClass().getName())) { // Remove this module as a listener of group events
try { GroupEventDispatcher.removeListener(this);
Class c = ClassUtils.forName(className); }
provider = (RosterItemProvider) c.newInstance();
} public static RosterItemProvider getRosterItemProvider() {
catch (Exception e) { return XMPPServer.getInstance().getRosterManager().provider;
Log.error("Error loading roster provider: " + className, e); }
provider = new DefaultRosterItemProvider();
} private void initProvider() {
} JiveGlobals.migrateProperty("provider.roster.className");
String className = JiveGlobals.getProperty("provider.roster.className",
} "org.jivesoftware.openfire.roster.DefaultRosterItemProvider");
} if (provider == null || !className.equals(provider.getClass().getName())) {
try {
Class c = ClassUtils.forName(className);
provider = (RosterItemProvider) c.newInstance();
}
catch (Exception e) {
Log.error("Error loading roster provider: " + className, e);
provider = new DefaultRosterItemProvider();
}
}
}
}
...@@ -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