Commit cf6444ba authored by Gaston Dombiak's avatar Gaston Dombiak Committed by gaston

Added shared groups support. JM-22


git-svn-id: http://svn.igniterealtime.org/svn/repos/messenger/trunk@781 b35dd754-fafc-0310-a699-88a17e54d16e
parent 03681b42
......@@ -15,9 +15,11 @@ import org.jivesoftware.util.Log;
import org.jivesoftware.util.Cacheable;
import org.jivesoftware.util.CacheSizes;
import org.jivesoftware.database.DbConnectionManager;
import org.jivesoftware.messenger.XMPPServer;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
......@@ -46,8 +48,8 @@ public class Group implements Cacheable {
private String name;
private String description;
private Map<String, String> properties;
private Collection<String> members;
private Collection<String> administrators;
private Collection<String> members = new CopyOnWriteArrayList<String>();
private Collection<String> administrators = new CopyOnWriteArrayList<String>();
/**
* Constructs a new group.
......@@ -55,7 +57,7 @@ public class Group implements Cacheable {
* @param provider the group provider.
* @param name the name.
* @param description the description.
* @param members a Collection of the group members (includes administrators).
* @param members a Collection of the group members.
* @param administrators a Collection of the group administrators.
*/
protected Group(GroupProvider provider, String name, String description,
......@@ -65,8 +67,8 @@ public class Group implements Cacheable {
this.groupManager = GroupManager.getInstance();
this.name = name;
this.description = description;
this.members = members;
this.administrators = administrators;
this.members.addAll(members);
this.administrators.addAll(administrators);
}
/**
......@@ -142,23 +144,35 @@ public class Group implements Cacheable {
}
/**
* Returns a Collection of the group members that are administrators.
* Returns a Collection of the group administrators. Use <code>getUsers()</code> to get the
* complete list of group users.
*
* @return a Collection of the group administrators.
*/
public Collection<String> getAdministrators() {
// Return a wrapper that will intercept add and remove commands.
return new MemberCollection(administrators, true);
public Collection<String> getAdmins() {
return Collections.unmodifiableCollection(administrators);
}
/**
* Returns a Collection of the group members.
* Returns a Collection of the group members. Use <code>getUsers()</code> to get the complete
* list of group users.
*
* @return a Collection of the group members.
*/
public Collection<String> getMembers() {
// Return a wrapper that will intercept add and remove commands.
return new MemberCollection(members, false);
return Collections.unmodifiableCollection(members);
}
/**
* Returns a Collection with all the group users. The collection will include group members
* as well as group administrators.
*
* @return a Collection with all the group users.
*/
public Collection<String> getUsers() {
Collection<String> answer = new ArrayList<String>(members);
answer.addAll(administrators);
return Collections.unmodifiableCollection(answer);
}
/**
......@@ -171,65 +185,112 @@ public class Group implements Cacheable {
return members.contains(username) || administrators.contains(username);
}
public int getCachedSize() {
// Approximate the size of the object in bytes by calculating the size
// of each field.
int size = 0;
size += CacheSizes.sizeOfObject(); // overhead of object
size += CacheSizes.sizeOfString(name);
size += CacheSizes.sizeOfString(description);
return size;
/**
* Returns true if the provided username belongs to a member of the group.
*
* @param username the username to check.
* @return true if the provided username belongs to a member of the group.
*/
public boolean isMember(String username) {
return members.contains(username);
}
/**
* Collection implementation that notifies the GroupProvider of any
* changes to the collection.
* Returns true if the provided username belongs to an administrator of the group.
*
* @param username the username to check.
* @return true if the provided username belongs to an administrator of the group.
*/
private class MemberCollection extends AbstractCollection {
private Collection<String> users;
private boolean adminCollection;
public boolean isAdmin(String username) {
return administrators.contains(username);
}
public MemberCollection(Collection<String> users, boolean adminCollection) {
this.users = users;
this.adminCollection = adminCollection;
/**
* Adds a new user as a member of the group. The roster of all group users that are currently
* logged into the server will be updated.
*
* @param user the user to add as a member of the goup.
*/
public void addMember(String user) {
if (members.contains(user)) {
return;
}
members.add(user);
userAdded(user, false);
}
public Iterator iterator() {
return new Iterator() {
Iterator iter = users.iterator();
Object current = null;
public boolean hasNext() {
return iter.hasNext();
}
public Object next() {
current = iter.next();
return current;
}
public void remove() {
if (current == null) {
throw new IllegalStateException();
}
provider.deleteMember(name, (String)current);
iter.remove();
}
};
/**
* Removes a member from the group. The roster of all group users that are currently
* logged into the server will be updated.
*
* @param user the user to remove as a member of the group.
*/
public void removeMember(String user) {
if (members.remove(user)) {
userRemoved(user);
}
}
public int size() {
return users.size();
/**
* Adds a new user as an administrator of the group. The roster of all group users that are
* currently logged into the server will be updated.
*
* @param user the user to add as an administrator of the goup.
*/
public void addAdmin(String user) {
if (administrators.contains(user)) {
return;
}
administrators.add(user);
userAdded(user, true);
}
public boolean add(Object member) {
provider.addMember(name, (String)member, adminCollection);
return users.add((String)member);
/**
* Removes an administrator from the group. The roster of all group users that are currently
* logged into the server will be updated.
*
* @param user the user to remove as an administrator of the group.
*/
public void removeAdmin(String user) {
if (administrators.remove(user)) {
userRemoved(user);
}
}
/**
* Update backend store and update group users' roster.
*
* @param user the user that was added to the group.
*/
private void userAdded(String user, boolean administrator) {
// Add the new group user to the backend store
provider.addMember(name, user, administrator);
// Update the group users' roster
XMPPServer.getInstance().getRosterManager().groupUserAdded(this, user);
}
/**
* Update backend store and update group users' roster.
*
* @param user the user that was removed from the group.
*/
private void userRemoved(String user) {
// Remove the group user from the backend store
provider.deleteMember(name, user);
// Update the group users' roster
XMPPServer.getInstance().getRosterManager().groupUserDeleted(this, user);
}
public int getCachedSize() {
// Approximate the size of the object in bytes by calculating the size
// of each field.
int size = 0;
size += CacheSizes.sizeOfObject(); // overhead of object
size += CacheSizes.sizeOfString(name);
size += CacheSizes.sizeOfString(description);
return size;
}
/**
* Map implementation that updates the database when properties are modified.
*/
......
......@@ -17,6 +17,8 @@ import org.jivesoftware.util.ClassUtils;
import org.jivesoftware.util.Log;
import org.jivesoftware.messenger.user.User;
import org.jivesoftware.messenger.JiveGlobals;
import org.jivesoftware.messenger.XMPPServer;
import org.jivesoftware.messenger.roster.RosterManager;
import java.util.Collection;
......@@ -118,6 +120,27 @@ public class GroupManager {
// Expire all relevant caches.
groupCache.remove(group.getName());
// Notify the RosterManager that the group has been deleted
RosterManager rosterManager = XMPPServer.getInstance().getRosterManager();
rosterManager.groupDeleted(group);
}
/**
* Deletes a user from all the groups where he/she belongs. The most probable cause for this
* request is that the user has been deleted from the system.
*
* @param user the deleted user from the system.
*/
public void deleteUser(User user) {
for (Group group : getGroups(user)) {
if (group.isAdmin(user.getUsername())) {
group.removeAdmin(user.getUsername());
}
else {
group.removeMember(user.getUsername());
}
}
}
/**
......
......@@ -18,6 +18,7 @@ import org.jivesoftware.messenger.forms.spi.XDataFormImpl;
import org.jivesoftware.messenger.forms.spi.XFormFieldImpl;
import org.jivesoftware.util.Log;
import org.jivesoftware.messenger.*;
import org.jivesoftware.messenger.group.GroupManager;
import org.jivesoftware.messenger.roster.RosterManager;
import org.jivesoftware.messenger.auth.UnauthorizedException;
import org.jivesoftware.messenger.user.*;
......@@ -202,10 +203,13 @@ public class IQRegisterHandler extends IQHandler implements ServerFeaturesProvid
Element iqElement = packet.getChildElement();
if (iqElement.element("remove") != null) {
if (session.getStatus() == Session.STATUS_AUTHENTICATED) {
User user = userManager.getUser(session.getUsername());
// Delete the user
userManager.deleteUser(userManager.getUser(session.getUsername()));
userManager.deleteUser(user);
// Delete the roster of the user
rosterManager.deleteRoster(session.getAddress());
// Delete the user from all the Groups
GroupManager.getInstance().deleteUser(user);
reply = IQ.createResultIQ(packet);
session.getConnection().deliver(reply);
......
......@@ -60,7 +60,7 @@ public class Roster implements Cacheable {
*
* <p>RosterItems that ONLY belong to shared groups won't be persistent unless the user
* explicitly subscribes to the contact's presence, renames the contact in his roster or adds
* the item to a personal group.</>
* the item to a personal group.</p>
*
* @param username The username of the user that owns this roster
*/
......@@ -88,7 +88,7 @@ public class Roster implements Cacheable {
for (Group group : sharedGroups) {
if (group.isUser(item.getJid().getNode())) {
// TODO Group name conflicts are not being considered (do we need this?)
item.addSharedGroups(group.getName());
item.addSharedGroup(group.getName());
}
}
rosterItems.put(item.getJid().toBareJID(), item);
......@@ -101,7 +101,7 @@ public class Roster implements Cacheable {
RosterItem item = new RosterItem(jid, RosterItem.SUB_BOTH, RosterItem.ASK_NONE,
RosterItem.RECV_NONE, user.getName(), null);
for (String group : sharedUsers.get(jid)) {
item.addSharedGroups(group);
item.addSharedGroup(group);
}
rosterItems.put(item.getJid().toBareJID(), item);
}
......@@ -269,19 +269,7 @@ public class Roster implements Cacheable {
// broadcast roster update
if (!(item.getSubStatus() == RosterItem.SUB_NONE
&& item.getAskStatus() == RosterItem.ASK_NONE)) {
// Set the groups to broadcast (include personal and shared groups)
List<String> groups = new ArrayList<String>(item.getGroups());
groups.addAll(item.getSharedGroups());
org.xmpp.packet.Roster roster = new org.xmpp.packet.Roster();
roster.setType(IQ.Type.set);
roster.addItem(item.getJid(), item.getNickname(),
getAskStatus(item.getAskStatus()),
org.xmpp.packet.Roster.Subscription.valueOf(item.getSubStatus().getName()),
groups);
broadcast(roster);
broadcast(item);
}
if (item.getSubStatus() == RosterItem.SUB_BOTH
|| item.getSubStatus() == RosterItem.SUB_TO) {
......@@ -296,14 +284,14 @@ public class Roster implements Cacheable {
* Remove a user from the roster.
*
* @param user the user to remove from the roster.
* @param forceRemove flag that indicates if checkings should be done before deleting the user.
* @param doChecking flag that indicates if checkings should be done before deleting the user.
* @return The roster item being removed or null if none existed
* @throws SharedGroupException if the user to remove belongs to a shared group
*/
public RosterItem deleteRosterItem(JID user, boolean forceRemove) throws SharedGroupException {
public RosterItem deleteRosterItem(JID user, boolean doChecking) throws SharedGroupException {
// Answer an error if user (i.e. contact) to delete belongs to a shared group
RosterItem itemToRemove = rosterItems.get(user.toBareJID());
if (itemToRemove.isShared()) {
if (doChecking && itemToRemove.isShared()) {
throw new SharedGroupException("Cannot remove contact that belongs to a shared group");
}
......@@ -413,12 +401,8 @@ public class Roster implements Cacheable {
// will have one entry in the map associated with all the groups
Map<JID,List<String>> sharedGroupUsers = new HashMap<JID,List<String>>();
for (Group group : sharedGroups) {
// Collect all the users of the group (i.e. members and administrators)
Collection<String> users = new ArrayList<String>(group.getAdministrators());
users.addAll(group.getMembers());
// Add the users of the group to the general list of users to process
for (String groupUser : users) {
for (String groupUser : group.getUsers()) {
// Add the user to the answer if the user doesn't belong to the personal roster
// (since we have already added the user to the answer)
JID jid = new JID(groupUser, XMPPServer.getInstance().getServerInfo().getName(),
......@@ -453,6 +437,20 @@ public class Roster implements Cacheable {
}
}
void broadcast(RosterItem item) {
// Set the groups to broadcast (include personal and shared groups)
List<String> groups = new ArrayList<String>(item.getGroups());
groups.addAll(item.getSharedGroups());
org.xmpp.packet.Roster roster = new org.xmpp.packet.Roster();
roster.setType(IQ.Type.set);
roster.addItem(item.getJid(), item.getNickname(),
getAskStatus(item.getAskStatus()),
org.xmpp.packet.Roster.Subscription.valueOf(item.getSubStatus().getName()),
groups);
broadcast(roster);
}
public int getCachedSize() {
// Approximate the size of the object in bytes by calculating the size
// of each field.
......@@ -462,4 +460,80 @@ public class Roster implements Cacheable {
size += CacheSizes.sizeOfString(username); // username
return size;
}
/**
* Update the roster since a group user has been added to a shared group. Create a new
* RosterItem if the there doesn't exist an item for the added user. The new RosterItem won't be
* saved to the backend store unless the user explicitly subscribes to the contact's presence,
* renames the contact in his roster or adds the item to a personal group. Otherwise the shared
* group will be added to the shared groups lists. In any case an update broadcast will be sent
* to all the users logged resources.
*
* @param sharedGroup the shared group where the user was added.
* @param addedUser the contact to update in the roster.
*/
void addSharedUser(String sharedGroup, String addedUser) {
JID jid = new JID(addedUser, XMPPServer.getInstance().getServerInfo().getName(), "");
try {
// Get the RosterItem for the *local* user to add
RosterItem item = getRosterItem(jid);
// Add the shared group to the list of shared groups
item.addSharedGroup(sharedGroup);
// Brodcast to all the user resources of the updated roster item
broadcast(item);
}
catch (UserNotFoundException e) {
try {
// Create a new RosterItem for this new user
User user = UserManager.getInstance().getUser(addedUser);
RosterItem item = new RosterItem(jid, RosterItem.SUB_BOTH, RosterItem.ASK_NONE,
RosterItem.RECV_NONE, user.getName(), null);
item.addSharedGroup(sharedGroup);
// Add the new item to the list of items
rosterItems.put(item.getJid().toBareJID(), item);
// Brodcast to all the user resources of the updated roster item
broadcast(item);
}
catch (UserNotFoundException ex) {
Log.error("Group (" + sharedGroup + ") includes non-existent username (" +
addedUser +
")");
}
}
}
/**
* Update the roster since a group user has been deleted from a shared group. If the RosterItem
* (of the deleted contact) exists only because of of the sahred group then the RosterItem will
* be deleted physically from the backend store. Otherwise the shared group will be removed from
* the shared groups lists. In any case an update broadcast will be sent to all the users
* logged resources.
*
* @param sharedGroup the shared group from where the user was deleted.
* @param deletedUser the contact to update in the roster.
*/
void deleteSharedUser(String sharedGroup, String deletedUser) {
JID jid = new JID(deletedUser, XMPPServer.getInstance().getServerInfo().getName(), "");
try {
// Get the RosterItem for the *local* user to remove
RosterItem item = getRosterItem(jid);
if (item.isOnlyShared() && item.getSharedGroups().size() == 1) {
// Delete the roster item from the roster since it exists only because of this
// group which is being removed
deleteRosterItem(jid, false);
}
else {
// Remove the removed shared group from the list of shared groups
item.removeSharedGroup(sharedGroup);
// Brodcast to all the user resources of the updated roster item
broadcast(item);
}
}
catch (SharedGroupException e) {
// Do nothing. Checkings are disabled so this exception should never happen.
}
catch (UserNotFoundException e) {
// Do nothing since the contact does not exist in the user's roster. (strange case!)
}
}
}
\ No newline at end of file
......@@ -354,10 +354,19 @@ public class RosterItem implements Cacheable {
*
* @param sharedGroup The shared group to add to the list of shared groups.
*/
public void addSharedGroups(String sharedGroup) {
public void addSharedGroup(String sharedGroup) {
sharedGroups.add(sharedGroup);
}
/**
* Removes a group from the shared groups list.
*
* @param sharedGroup The shared group to remove from the list of shared groups.
*/
public void removeSharedGroup(String sharedGroup) {
sharedGroups.remove(sharedGroup);
}
/**
* Returns true if this item belongs to a shared group. Return true even if the item belongs
* to a personal group and a shared group.
......
......@@ -17,6 +17,7 @@ import org.jivesoftware.util.CacheManager;
import org.jivesoftware.messenger.container.BasicModule;
import org.jivesoftware.messenger.user.UserNotFoundException;
import org.jivesoftware.messenger.SharedGroupException;
import org.jivesoftware.messenger.group.Group;
import java.util.Iterator;
......@@ -117,4 +118,57 @@ public class RosterManager extends BasicModule {
// Do nothing
}
}
/**
* Notification that a Group has been deleted. Update the group users' roster accordingly.
*
* @param group the group that has been deleted.
*/
public void groupDeleted(Group group) {
// Iterate on all the group users and update their rosters
for (String deletedUser : group.getUsers()) {
groupUserDeleted(group, deletedUser);
}
}
/**
* Notification that a Group user has been added. Update the group users' roster accordingly.
*
* @param group the group where the user was added.
* @param addedUser the username of the user that has been added to the group.
*/
public void groupUserAdded(Group group, String addedUser) {
// Iterate on all the group users and update their rosters
for (String userToUpdate : group.getUsers()) {
if (!addedUser.equals(userToUpdate)) {
// Get the roster to update
Roster roster = (Roster)CacheManager.getCache("username2roster").get(userToUpdate);
// Only update rosters in memory
if (roster != null) {
roster.addSharedUser(group.getName(), addedUser);
}
}
}
}
/**
* 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 deletedUser the username of the user that has been deleted from the group.
*/
public void groupUserDeleted(Group group, String deletedUser) {
// Iterate on all the group users and update their rosters
for (String userToUpdate : group.getUsers()) {
if (!deletedUser.equals(userToUpdate)) {
// Get the roster to update
Roster roster = (Roster)CacheManager.getCache("username2roster").get(userToUpdate);
// Only update rosters in memory
if (roster != null) {
roster.deleteSharedUser(group.getName(), deletedUser);
}
}
}
}
}
\ No newline at end of file
......@@ -38,8 +38,6 @@ public class DefaultUserProvider implements UserProvider {
private static final String INSERT_USER =
"INSERT INTO jiveUser (username,password,name,email,creationDate,modificationDate) " +
"VALUES (?,?,?,?,?,?)";
private static final String DELETE_USER_GROUPS =
"DELETE FROM jiveGroupUser WHERE username=?";
private static final String DELETE_USER_PROPS =
"DELETE FROM jiveUserProp WHERE username=?";
private static final String DELETE_VCARD_PROPS =
......@@ -140,11 +138,6 @@ public class DefaultUserProvider implements UserProvider {
boolean abortTransaction = false;
try {
con = DbConnectionManager.getTransactionConnection();
// Remove user from all groups
pstmt = con.prepareStatement(DELETE_USER_GROUPS);
pstmt.setString(1, username);
pstmt.execute();
pstmt.close();
// Delete all of the users's extended properties
pstmt = con.prepareStatement(DELETE_USER_PROPS);
pstmt.setString(1, username);
......
......@@ -139,7 +139,7 @@ Total Groups: <%= webManager.getGroupManager().getGroupCount() %>,
<%= group.getMembers().size() %>
</td>
<td width="10%" align="center">
<%= group.getAdministrators().size() %>
<%= group.getAdmins().size() %>
</td>
<td width="1%" align="center">
<a href="group-edit-form.jsp?group=<%= group.getName() %>"
......
......@@ -13,7 +13,8 @@
org.jivesoftware.messenger.user.*,
org.jivesoftware.admin.*,
org.xmpp.packet.JID,
java.net.URLEncoder"
java.net.URLEncoder,
org.jivesoftware.messenger.group.GroupManager"
errorPage="error.jsp"
%>
......@@ -42,7 +43,10 @@
webManager.getUserManager().deleteUser(user);
// Delete the user's roster
JID userAddress = new JID(username, webManager.getServerInfo().getName(), null);
// Delete the roster of the user
webManager.getRosterManager().deleteRoster(userAddress);
// Delete the user from all the Groups
GroupManager.getInstance().deleteUser(user);
// Deleted your own user account, force login
if (username.equals(webManager.getAuthToken().getUsername())){
session.removeAttribute("jive.admin.authToken");
......
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