From c3cadff7055655ec9d497d6eea5424dd6022ced5 Mon Sep 17 00:00:00 2001 From: Brian Clements Date: Tue, 18 Feb 2014 09:49:40 -0600 Subject: [PATCH] Issue #2751 implemented room/topic ownership transfer Former-commit-id: cf5041f2d052c4900e2871e0fc8dc1a2b71794de --- .../provider/event/LeaderChangeEvent.java | 23 +++- .../session/ChangeAffiliationExtension.java | 92 +++++++++++++ .../provider/session/PubSubOperations.java | 68 ++++++++++ .../session/SharedDisplaySession.java | 127 ++++++++++++++---- .../comm/provider/session/VenueSession.java | 58 +++++--- .../display/data/SessionContainer.java | 4 +- .../ui/session/CollaborationSessionView.java | 26 +++- 7 files changed, 346 insertions(+), 52 deletions(-) create mode 100644 cave/com.raytheon.uf.viz.collaboration.comm/src/com/raytheon/uf/viz/collaboration/comm/provider/session/ChangeAffiliationExtension.java create mode 100644 cave/com.raytheon.uf.viz.collaboration.comm/src/com/raytheon/uf/viz/collaboration/comm/provider/session/PubSubOperations.java diff --git a/cave/com.raytheon.uf.viz.collaboration.comm/src/com/raytheon/uf/viz/collaboration/comm/provider/event/LeaderChangeEvent.java b/cave/com.raytheon.uf.viz.collaboration.comm/src/com/raytheon/uf/viz/collaboration/comm/provider/event/LeaderChangeEvent.java index f32ea6e442..e4a022d80a 100644 --- a/cave/com.raytheon.uf.viz.collaboration.comm/src/com/raytheon/uf/viz/collaboration/comm/provider/event/LeaderChangeEvent.java +++ b/cave/com.raytheon.uf.viz.collaboration.comm/src/com/raytheon/uf/viz/collaboration/comm/provider/event/LeaderChangeEvent.java @@ -33,6 +33,7 @@ import com.raytheon.uf.viz.collaboration.comm.provider.user.VenueParticipant; * Date Ticket# Engineer Description * ------------ ---------- ----------- -------------------------- * Feb 11, 2014 njensen Initial creation + * Feb 19, 2014 2751 bclement added oldLeader field * * * @@ -46,12 +47,17 @@ public class LeaderChangeEvent { @DynamicSerializeElement private VenueParticipant newLeader; + @DynamicSerializeElement + private VenueParticipant oldLeader; + public LeaderChangeEvent() { } - public LeaderChangeEvent(VenueParticipant newLeader) { + public LeaderChangeEvent(VenueParticipant newLeader, + VenueParticipant oldLeader) { this.newLeader = newLeader; + this.oldLeader = oldLeader; } public VenueParticipant getNewLeader() { @@ -62,4 +68,19 @@ public class LeaderChangeEvent { this.newLeader = newLeader; } + /** + * @return the oldLeader + */ + public VenueParticipant getOldLeader() { + return oldLeader; + } + + /** + * @param oldLeader + * the oldLeader to set + */ + public void setOldLeader(VenueParticipant oldLeader) { + this.oldLeader = oldLeader; + } + } diff --git a/cave/com.raytheon.uf.viz.collaboration.comm/src/com/raytheon/uf/viz/collaboration/comm/provider/session/ChangeAffiliationExtension.java b/cave/com.raytheon.uf.viz.collaboration.comm/src/com/raytheon/uf/viz/collaboration/comm/provider/session/ChangeAffiliationExtension.java new file mode 100644 index 0000000000..3faceb794d --- /dev/null +++ b/cave/com.raytheon.uf.viz.collaboration.comm/src/com/raytheon/uf/viz/collaboration/comm/provider/session/ChangeAffiliationExtension.java @@ -0,0 +1,92 @@ +/** + * This software was developed and / or modified by Raytheon Company, + * pursuant to Contract DG133W-05-CQ-1067 with the US Government. + * + * U.S. EXPORT CONTROLLED TECHNICAL DATA + * This software product contains export-restricted data whose + * export/transfer/disclosure is restricted by U.S. law. Dissemination + * to non-U.S. persons whether in the United States or abroad requires + * an export license or other authorization. + * + * Contractor Name: Raytheon Company + * Contractor Address: 6825 Pine Street, Suite 340 + * Mail Stop B8 + * Omaha, NE 68106 + * 402.291.0100 + * + * See the AWIPS II Master Rights File ("Master Rights File.pdf") for + * further licensing information. + **/ +package com.raytheon.uf.viz.collaboration.comm.provider.session; + +import org.jivesoftware.smackx.pubsub.Affiliation; +import org.jivesoftware.smackx.pubsub.Node; +import org.jivesoftware.smackx.pubsub.NodeExtension; +import org.jivesoftware.smackx.pubsub.PubSubElementType; + +/** + * Packet extension for changing a user's affiliation with a pubsub topic. + * Follows specification at + * http://xmpp.org/extensions/xep-0060.html#owner-affiliations-modify + * + *
+ * 
+ * SOFTWARE HISTORY
+ * 
+ * Date         Ticket#    Engineer    Description
+ * ------------ ---------- ----------- --------------------------
+ * Feb 18, 2014 2751       bclement     Initial creation
+ * 
+ * 
+ * + * @author bclement + * @version 1.0 + */ +public class ChangeAffiliationExtension extends NodeExtension { + + private static final String affiliationName = "affiliation"; + + private static final String jidAttribute = "jid"; + + private final String id; + + private final Affiliation.Type type; + + + /** + * @param n + * topic node + * @param id + * userid + * @param type + * new affiliation + */ + public ChangeAffiliationExtension(Node n, String id, Affiliation.Type type) { + super(PubSubElementType.AFFILIATIONS, n.getId()); + this.id = id; + this.type = type; + } + + /* + * (non-Javadoc) + * + * @see org.jivesoftware.smackx.pubsub.NodeExtension#toXML() + */ + @Override + public String toXML() { + StringBuilder builder = new StringBuilder("<"); + builder.append(getElementName()); + builder.append(" node='"); + builder.append(getNode()); + builder.append("'><").append(affiliationName).append(" "); + builder.append(jidAttribute).append("='"); + builder.append(this.id); + builder.append("' ").append(affiliationName).append("='"); + builder.append(this.type.toString()); + builder.append("'/>"); + return builder.toString(); + } + +} diff --git a/cave/com.raytheon.uf.viz.collaboration.comm/src/com/raytheon/uf/viz/collaboration/comm/provider/session/PubSubOperations.java b/cave/com.raytheon.uf.viz.collaboration.comm/src/com/raytheon/uf/viz/collaboration/comm/provider/session/PubSubOperations.java new file mode 100644 index 0000000000..1fd30e4f24 --- /dev/null +++ b/cave/com.raytheon.uf.viz.collaboration.comm/src/com/raytheon/uf/viz/collaboration/comm/provider/session/PubSubOperations.java @@ -0,0 +1,68 @@ +/** + * This software was developed and / or modified by Raytheon Company, + * pursuant to Contract DG133W-05-CQ-1067 with the US Government. + * + * U.S. EXPORT CONTROLLED TECHNICAL DATA + * This software product contains export-restricted data whose + * export/transfer/disclosure is restricted by U.S. law. Dissemination + * to non-U.S. persons whether in the United States or abroad requires + * an export license or other authorization. + * + * Contractor Name: Raytheon Company + * Contractor Address: 6825 Pine Street, Suite 340 + * Mail Stop B8 + * Omaha, NE 68106 + * 402.291.0100 + * + * See the AWIPS II Master Rights File ("Master Rights File.pdf") for + * further licensing information. + **/ +package com.raytheon.uf.viz.collaboration.comm.provider.session; + +import org.jivesoftware.smack.XMPPConnection; +import org.jivesoftware.smack.XMPPException; +import org.jivesoftware.smack.packet.IQ; +import org.jivesoftware.smackx.pubsub.packet.PubSub; +import org.jivesoftware.smackx.pubsub.packet.PubSubNamespace; +import org.jivesoftware.smackx.pubsub.packet.SyncPacketSend; + +/** + * PubSub operations not implemented by the Smack PubSub manager. This includes + * operations to modify ownership of a topic node. + * + *
+ * 
+ * SOFTWARE HISTORY
+ * 
+ * Date         Ticket#    Engineer    Description
+ * ------------ ---------- ----------- --------------------------
+ * Feb 18, 2014 2751      bclement     Initial creation
+ * 
+ * 
+ * + * @author bclement + * @version 1.0 + */ +public class PubSubOperations { + + private PubSubOperations() { + } + + /** + * Send packet to change affiliation of user on a pubsub topic node + * + * @param conn + * @param affiliation + * @throws XMPPException + */ + public static void sendAffiliationPacket(XMPPConnection conn, + ChangeAffiliationExtension affiliation) throws XMPPException { + PubSub packet = new PubSub(); + packet.setType(IQ.Type.SET); + packet.setTo("pubsub." + conn.getServiceName()); + packet.setPubSubNamespace(PubSubNamespace.OWNER); + packet.addExtension(affiliation); + SyncPacketSend.getReply(conn, packet); + } + +} diff --git a/cave/com.raytheon.uf.viz.collaboration.comm/src/com/raytheon/uf/viz/collaboration/comm/provider/session/SharedDisplaySession.java b/cave/com.raytheon.uf.viz.collaboration.comm/src/com/raytheon/uf/viz/collaboration/comm/provider/session/SharedDisplaySession.java index 5ea3324861..bad4d28e2f 100644 --- a/cave/com.raytheon.uf.viz.collaboration.comm/src/com/raytheon/uf/viz/collaboration/comm/provider/session/SharedDisplaySession.java +++ b/cave/com.raytheon.uf.viz.collaboration.comm/src/com/raytheon/uf/viz/collaboration/comm/provider/session/SharedDisplaySession.java @@ -79,6 +79,7 @@ import com.raytheon.uf.viz.collaboration.comm.provider.user.VenueParticipant; * Feb 12, 2014 2793 bclement added additional null check to sendObjectToVenue * Feb 13, 2014 2751 bclement VenueParticipant refactor * Feb 13, 2014 2751 njensen Added changeLeader() + * Feb 18, 2014 2751 bclement implemented room and pubsub ownership transfer * Feb 19, 2014 2751 bclement added isClosed() * * @@ -173,7 +174,7 @@ public class SharedDisplaySession extends VenueSession implements * #sendObjectToVenue(java.lang.Object) */ @Override - public void sendObjectToVenue(Object obj) throws CollaborationException { + public void sendObjectToVenue(Object obj){ if (obj == null) { return; } @@ -196,8 +197,7 @@ public class SharedDisplaySession extends VenueSession implements * java.lang.Object) */ @Override - public void sendObjectToPeer(VenueParticipant participant, Object obj) - throws CollaborationException { + public void sendObjectToPeer(VenueParticipant participant, Object obj) { // TODO should only send to CAVE clients if (obj == null) { return; @@ -412,10 +412,24 @@ public class SharedDisplaySession extends VenueSession implements if (payload instanceof SessionPayload) { SessionPayload sp = (SessionPayload) payload; Object obj = sp.getData(); + if (obj instanceof LeaderChangeEvent) { + handleLeaderChange((LeaderChangeEvent) obj); + } postEvent(obj); } } + /** + * Apply leadership change event to session + * + * @param event + */ + private void handleLeaderChange(LeaderChangeEvent event) { + VenueParticipant newLeader = event.getNewLeader(); + setCurrentDataProvider(newLeader); + setCurrentSessionLeader(newLeader); + } + /** * @return true if session data topic exists */ @@ -491,53 +505,114 @@ public class SharedDisplaySession extends VenueSession implements // we don't persist data packets to topics } + /** + * Make user with id an owner of the pubsub topic + * + * @param id + * @throws XMPPException + */ + private void grantTopicOwnership(String id) throws XMPPException { + sendAffiliationPacket(id, Affiliation.Type.owner); + } + + /** + * Make user with id a publisher of the pubsub topic + * + * @param id + * @throws XMPPException + */ + private void revokeTopicOwnership(String id) throws XMPPException { + sendAffiliationPacket(id, Affiliation.Type.publisher); + } + + /** + * Change affiliation of user with id + * + * @param id + * @param type + * @throws XMPPException + */ + private void sendAffiliationPacket(String id, Affiliation.Type type) + throws XMPPException { + ChangeAffiliationExtension affiliation = new ChangeAffiliationExtension( + topic, id, type); + PubSubOperations.sendAffiliationPacket(conn, affiliation); + } + @Override public void changeLeader(VenueParticipant newLeader) throws CollaborationException { - if (!getCurrentDataProvider().isSameUser(getUserID())) { + final VenueParticipant oldLeader = getCurrentDataProvider(); + if (!oldLeader.isSameUser(getUserID())) { throw new CollaborationException( "Only the leader can transfer leadership"); } + if (!newLeader.hasActualUserId()) { + throw new CollaborationException( + "Unable to grant ownership because new leader's actual userid is not known"); + } - boolean ownershipGranted = false; + final String newLeaderId = newLeader.getUserid().getNormalizedId(); + + boolean topicOwnershipGranted = false; + boolean roomOwnershipGranted = false; boolean othersNotified = false; + String revokeTarget = null; try { // was formerly the data provider, so hand off pubsub ownership - // TODO change pubsub ownership + grantTopicOwnership(newLeaderId); + topicOwnershipGranted = true; // change the room's ownership cause the leader needs to know // participants' jids to properly complete some leader actions - // FIXME - // muc.grantOwnership(newLeader.getFQName()); - ownershipGranted = true; + muc.grantOwnership(newLeaderId); + roomOwnershipGranted = true; // TODO if we get private chat within a chat room working, we // shouldn't need to let everyone know the leader's jid - LeaderChangeEvent event = new LeaderChangeEvent(newLeader); + LeaderChangeEvent event = new LeaderChangeEvent(newLeader, + oldLeader); this.sendObjectToVenue(event); othersNotified = true; - // TODO revoke pubsub ownership - // revoke our own ownership, last action in case other parts // of the transfer fail - // FIXME - // muc.revokeOwnership(getUserID().getFQName()); - } catch (Exception e) { - // TODO change to catch XMPPConnection, ensure this is transactional - if (ownershipGranted && !othersNotified) { - // transaction, attempt to roll back the ownership change + revokeTarget = "topic"; + UserId account = getAccount(); + revokeTopicOwnership(account.getNormalizedId()); + // we revoke admin instead of ownership because it sets back to + // 'member' instead of just down to 'admin' + revokeTarget = "room"; + muc.revokeAdmin(account.getNormalizedId()); + } catch (XMPPException e) { + if (!othersNotified) { + // transaction, attempt to roll back the ownership changes // because other participants didn't hear about it - try { - muc.revokeAdmin(newLeader.getFQName()); - } catch (XMPPException e1) { - // TODO we only want to throw up the original error, not - // the rollback error, is printStackTrace good enough? - e1.printStackTrace(); + if (roomOwnershipGranted) { + try { + muc.revokeAdmin(newLeaderId); + } catch (XMPPException e1) { + log.error( + "Problem rolling back room ownership transfer", + e1); + } } + if (topicOwnershipGranted) { + try{ + revokeTopicOwnership(newLeaderId); + } catch (XMPPException e1) { + log.error( + "Problem rolling back topic ownership transfer", + e1); + } + } + throw new CollaborationException( + "Error transferring leadership", e); + } else { + log.warn("Problem releasing ownership of " + revokeTarget + + ". " + e.getLocalizedMessage()); } - throw new CollaborationException("Error transferring leadership", e); - } + } } /* diff --git a/cave/com.raytheon.uf.viz.collaboration.comm/src/com/raytheon/uf/viz/collaboration/comm/provider/session/VenueSession.java b/cave/com.raytheon.uf.viz.collaboration.comm/src/com/raytheon/uf/viz/collaboration/comm/provider/session/VenueSession.java index 63694249cd..13fab82701 100644 --- a/cave/com.raytheon.uf.viz.collaboration.comm/src/com/raytheon/uf/viz/collaboration/comm/provider/session/VenueSession.java +++ b/cave/com.raytheon.uf.viz.collaboration.comm/src/com/raytheon/uf/viz/collaboration/comm/provider/session/VenueSession.java @@ -41,6 +41,8 @@ import org.jivesoftware.smackx.muc.UserStatusListener; import org.jivesoftware.smackx.packet.DiscoverItems; import com.google.common.eventbus.EventBus; +import com.raytheon.uf.common.status.IUFStatusHandler; +import com.raytheon.uf.common.status.UFStatus; import com.raytheon.uf.viz.collaboration.comm.Activator; import com.raytheon.uf.viz.collaboration.comm.identity.CollaborationException; import com.raytheon.uf.viz.collaboration.comm.identity.IMessage; @@ -95,6 +97,7 @@ import com.raytheon.uf.viz.collaboration.comm.provider.user.VenueParticipant; * Jan 30, 2014 2698 bclement changed UserId to VenueParticipant, added handle * Feb 13, 2014 2751 bclement VenueParticipant refactor * Feb 18, 2014 2751 bclement Fixed history message 'from' type + * Feb 18, 2014 2751 bclement log privilege changes instead of spamming chat window * * * @@ -107,6 +110,9 @@ import com.raytheon.uf.viz.collaboration.comm.provider.user.VenueParticipant; public class VenueSession extends BaseSession implements IVenueSession { + private static final IUFStatusHandler log = UFStatus + .getHandler(VenueSession.class); + private static final String SEND_TXT = "[[TEXT]]"; public static final String SEND_HISTORY = "[[HISTORY]]"; @@ -431,13 +437,13 @@ public class VenueSession extends BaseSession implements IVenueSession { @Override public void ownershipRevoked(String participant) { - sendParticipantEvent(participant, ParticipantEventType.UPDATED, + logParticipantEvent(participant, ParticipantEventType.UPDATED, "is no longer a room owner."); } @Override public void ownershipGranted(String participant) { - sendParticipantEvent(participant, ParticipantEventType.UPDATED, + logParticipantEvent(participant, ParticipantEventType.UPDATED, "is now a room owner."); } @@ -450,25 +456,25 @@ public class VenueSession extends BaseSession implements IVenueSession { @Override public void moderatorRevoked(String participant) { - sendParticipantEvent(participant, ParticipantEventType.UPDATED, + logParticipantEvent(participant, ParticipantEventType.UPDATED, "is no longer a moderator."); } @Override public void moderatorGranted(String participant) { - sendParticipantEvent(participant, ParticipantEventType.UPDATED, + logParticipantEvent(participant, ParticipantEventType.UPDATED, "is now a moderator."); } @Override public void membershipRevoked(String participant) { - sendParticipantEvent(participant, ParticipantEventType.UPDATED, + logParticipantEvent(participant, ParticipantEventType.UPDATED, "is no longer a member of the room."); } @Override public void membershipGranted(String participant) { - sendParticipantEvent(participant, ParticipantEventType.UPDATED, + logParticipantEvent(participant, ParticipantEventType.UPDATED, "is now a member of the room."); } @@ -502,16 +508,26 @@ public class VenueSession extends BaseSession implements IVenueSession { @Override public void adminRevoked(String participant) { - sendParticipantEvent(participant, ParticipantEventType.UPDATED, + logParticipantEvent(participant, ParticipantEventType.UPDATED, "is no longer an admin."); } @Override public void adminGranted(String participant) { - sendParticipantEvent(participant, ParticipantEventType.UPDATED, + logParticipantEvent(participant, ParticipantEventType.UPDATED, "is now an admin."); } + private void logParticipantEvent(String participant, + ParticipantEventType type, String desciption) { + StringBuilder builder = new StringBuilder(); + IVenue v = getVenue(); + builder.append("In session '").append(v.getName()) + .append("': "); + builder.append(participant).append(" ").append(desciption); + log.debug(builder.toString()); + } + private void sendParticipantEvent(String participant, ParticipantEventType type, String desciption) { VenueParticipant user = IDConverter.convertFromRoom(muc, @@ -577,32 +593,32 @@ public class VenueSession extends BaseSession implements IVenueSession { @Override public void ownershipRevoked() { - sendUserEvent("You are no longer an owner of this room."); + logUserEvent("You are no longer an owner of this room."); } @Override public void ownershipGranted() { - sendUserEvent("You are now an owner of this room."); + logUserEvent("You are now an owner of this room."); } @Override public void moderatorRevoked() { - sendUserEvent("You are no longer a moderator of this room."); + logUserEvent("You are no longer a moderator of this room."); } @Override public void moderatorGranted() { - sendUserEvent("You are now the moderator of this room."); + logUserEvent("You are now the moderator of this room."); } @Override public void membershipRevoked() { - sendUserEvent("You are no longer a member of this room."); + logUserEvent("You are no longer a member of this room."); } @Override public void membershipGranted() { - sendUserEvent("You are now a member of this room."); + logUserEvent("You are now a member of this room."); } @Override @@ -623,17 +639,27 @@ public class VenueSession extends BaseSession implements IVenueSession { @Override public void adminRevoked() { - sendUserEvent("You have had admin privileges revoked."); + logUserEvent("You have had admin privileges revoked."); } @Override public void adminGranted() { - sendUserEvent("You have had admin privileges granted."); + logUserEvent("You have had admin privileges granted."); } private void sendUserEvent(String message) { postEvent(new VenueUserEvent(message)); } + + private void logUserEvent(String message) { + StringBuilder builder = new StringBuilder(); + IVenue v = getVenue(); + builder.append("In session '").append(v.getName()) + .append("': "); + builder.append(message); + log.info(builder.toString()); + } + }); } diff --git a/cave/com.raytheon.uf.viz.collaboration.display/src/com/raytheon/uf/viz/collaboration/display/data/SessionContainer.java b/cave/com.raytheon.uf.viz.collaboration.display/src/com/raytheon/uf/viz/collaboration/display/data/SessionContainer.java index 3b891de2d0..3154b0f19f 100644 --- a/cave/com.raytheon.uf.viz.collaboration.display/src/com/raytheon/uf/viz/collaboration/display/data/SessionContainer.java +++ b/cave/com.raytheon.uf.viz.collaboration.display/src/com/raytheon/uf/viz/collaboration/display/data/SessionContainer.java @@ -135,9 +135,7 @@ public class SessionContainer { // for now (and possibly forever) we are not allowing capabilities to be // transferred separately VenueParticipant newLeader = event.getNewLeader(); - VenueParticipant oldLeader = session.getCurrentDataProvider(); - session.setCurrentDataProvider(newLeader); - session.setCurrentSessionLeader(newLeader); + VenueParticipant oldLeader = event.getOldLeader(); if (session.getUserID().isSameUser(oldLeader)) { // just gave up our leadership diff --git a/cave/com.raytheon.uf.viz.collaboration.ui/src/com/raytheon/uf/viz/collaboration/ui/session/CollaborationSessionView.java b/cave/com.raytheon.uf.viz.collaboration.ui/src/com/raytheon/uf/viz/collaboration/ui/session/CollaborationSessionView.java index f3836e96be..8a8a65642c 100644 --- a/cave/com.raytheon.uf.viz.collaboration.ui/src/com/raytheon/uf/viz/collaboration/ui/session/CollaborationSessionView.java +++ b/cave/com.raytheon.uf.viz.collaboration.ui/src/com/raytheon/uf/viz/collaboration/ui/session/CollaborationSessionView.java @@ -54,6 +54,7 @@ import com.raytheon.uf.viz.collaboration.comm.identity.ISharedDisplaySession; import com.raytheon.uf.viz.collaboration.comm.identity.IVenueSession; import com.raytheon.uf.viz.collaboration.comm.identity.invite.ColorPopulator; import com.raytheon.uf.viz.collaboration.comm.identity.user.SharedDisplayRole; +import com.raytheon.uf.viz.collaboration.comm.provider.event.LeaderChangeEvent; import com.raytheon.uf.viz.collaboration.comm.provider.user.VenueParticipant; import com.raytheon.uf.viz.collaboration.display.IRemoteDisplayContainer; import com.raytheon.uf.viz.collaboration.display.IRemoteDisplayContainer.IRemoteDisplayChangedListener; @@ -94,6 +95,7 @@ import com.raytheon.viz.ui.input.EditableManager; * Jan 28, 2014 2698 bclement removed venue info * Jan 30, 2014 2698 bclement changed UserId to VenueParticipant * Feb 12, 2014 2751 njensen Added transfer leadership and shutdown safety + * Feb 18, 2014 2751 bclement update participants list and notify on leader change * * * @@ -835,11 +837,23 @@ public class CollaborationSessionView extends SessionView implements if (newDisplayContainer != null) { newDisplayContainer.addRemoteDisplayChangedListener(this); } - - // this really only happens when the leader changes, so refresh - if (this.usersTable != null && !usersTable.getTable().isDisposed()) { - this.usersTable.refresh(); - } - updateToolItems(); } + + @Subscribe + public void leaderChanged(final LeaderChangeEvent event) { + VizApp.runAsync(new Runnable() { + @Override + public void run() { + if (usersTable != null + && !usersTable.getTable().isDisposed()) { + usersTable.setInput(session.getVenue().getParticipants()); + usersTable.refresh(); + } + sendParticipantSystemMessage(event.getNewLeader(), + " is now leader."); + updateToolItems(); + } + }); + } + }