Issue #2751 implemented room/topic ownership transfer

Former-commit-id: cf5041f2d052c4900e2871e0fc8dc1a2b71794de
This commit is contained in:
Brian Clements 2014-02-18 09:49:40 -06:00
parent c13942140a
commit c3cadff705
7 changed files with 346 additions and 52 deletions

View file

@ -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
*
* </pre>
*
@ -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;
}
}

View file

@ -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
*
* <pre>
*
* SOFTWARE HISTORY
*
* Date Ticket# Engineer Description
* ------------ ---------- ----------- --------------------------
* Feb 18, 2014 2751 bclement Initial creation
*
* </pre>
*
* @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("'/></");
builder.append(getElementName());
builder.append(">");
return builder.toString();
}
}

View file

@ -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.
*
* <pre>
*
* SOFTWARE HISTORY
*
* Date Ticket# Engineer Description
* ------------ ---------- ----------- --------------------------
* Feb 18, 2014 2751 bclement Initial creation
*
* </pre>
*
* @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);
}
}

View file

@ -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()
*
* </pre>
@ -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,52 +505,113 @@ 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);
}
}

View file

@ -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
*
* </pre>
*
@ -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());
}
});
}

View file

@ -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

View file

@ -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
*
* </pre>
*
@ -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();
}
});
}
}