diff --git a/cave/com.raytheon.uf.viz.collaboration.comm/src/com/raytheon/uf/viz/collaboration/comm/identity/ISharedDisplaySession.java b/cave/com.raytheon.uf.viz.collaboration.comm/src/com/raytheon/uf/viz/collaboration/comm/identity/ISharedDisplaySession.java index af3affa717..7173441a2a 100644 --- a/cave/com.raytheon.uf.viz.collaboration.comm/src/com/raytheon/uf/viz/collaboration/comm/identity/ISharedDisplaySession.java +++ b/cave/com.raytheon.uf.viz.collaboration.comm/src/com/raytheon/uf/viz/collaboration/comm/identity/ISharedDisplaySession.java @@ -36,6 +36,7 @@ import com.raytheon.uf.viz.collaboration.comm.provider.user.VenueParticipant; * Feb 13, 2014 2751 bclement changed sendObjectToPeer id to VenueParticipant * Feb 13, 2014 2751 njensen Added changeLeader() * Feb 19, 2014 2751 bclement Added isClosed() + * Apr 15, 2014 2822 bclement added isSharedDisplayClient() * * * @@ -124,4 +125,10 @@ public interface ISharedDisplaySession extends IVenueSession { */ public boolean isClosed(); + /** + * @param participant + * @return true if the participant is viewing the shared display + */ + public boolean isSharedDisplayClient(VenueParticipant participant); + } diff --git a/cave/com.raytheon.uf.viz.collaboration.comm/src/com/raytheon/uf/viz/collaboration/comm/provider/connection/CollaborationConnection.java b/cave/com.raytheon.uf.viz.collaboration.comm/src/com/raytheon/uf/viz/collaboration/comm/provider/connection/CollaborationConnection.java index bab15aa72c..b6e34ad338 100644 --- a/cave/com.raytheon.uf.viz.collaboration.comm/src/com/raytheon/uf/viz/collaboration/comm/provider/connection/CollaborationConnection.java +++ b/cave/com.raytheon.uf.viz.collaboration.comm/src/com/raytheon/uf/viz/collaboration/comm/provider/connection/CollaborationConnection.java @@ -33,6 +33,10 @@ import org.jivesoftware.smack.packet.Presence; import org.jivesoftware.smack.packet.XMPPError; import org.jivesoftware.smack.provider.ProviderManager; import org.jivesoftware.smackx.muc.MultiUserChat; +import org.jivesoftware.smackx.pubsub.PubSubElementType; +import org.jivesoftware.smackx.pubsub.packet.PubSubNamespace; +import org.jivesoftware.smackx.pubsub.provider.SubscriptionProvider; +import org.jivesoftware.smackx.pubsub.provider.SubscriptionsProvider; import com.google.common.eventbus.EventBus; import com.google.common.net.HostAndPort; @@ -108,6 +112,7 @@ import com.raytheon.uf.viz.collaboration.comm.provider.user.VenueParticipant; * Apr 09, 2014 2785 mpduff Throw error when not connected and the connection should exist. * Apr 14, 2014 2903 bclement moved from session subpackage to connection, removed password from memory, * moved listeners to own classes, reworked connect/register listeners/login order + * Apr 15, 2014 2822 bclement added pubsub owner subscriptions provider registration * * * @@ -124,6 +129,19 @@ public class CollaborationConnection implements IEventPublisher { PacketConstants.COLLAB_XMLNS, new SessionPayloadProvider()); pm.addIQProvider(PacketConstants.QUERY_ELEMENT_NAME, AuthInfo.AUTH_QUERY_XMLNS, new AuthInfoProvider()); + /* + * smack doesn't support some of the OWNER operations such as getting + * all subscriptions on a node. PubSubOperations creates the request + * objects for these operations, but the response needs to be parsed. + * Here we register the existing smack parsers using the OWNER + * namespace. + */ + pm.addExtensionProvider( + PubSubElementType.SUBSCRIPTION.getElementName(), + PubSubNamespace.OWNER.getXmlns(), new SubscriptionProvider()); + pm.addExtensionProvider( + PubSubElementType.SUBSCRIPTIONS.getElementName(), + PubSubNamespace.OWNER.getXmlns(), new SubscriptionsProvider()); } private static final transient IUFStatusHandler statusHandler = UFStatus 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 index ddc9539c45..bfe388b680 100644 --- 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 @@ -19,9 +19,18 @@ **/ package com.raytheon.uf.viz.collaboration.comm.provider.session; +import java.util.List; + import org.jivesoftware.smack.XMPPConnection; import org.jivesoftware.smack.XMPPException; import org.jivesoftware.smack.packet.IQ; +import org.jivesoftware.smack.packet.IQ.Type; +import org.jivesoftware.smack.packet.Packet; +import org.jivesoftware.smackx.pubsub.Node; +import org.jivesoftware.smackx.pubsub.NodeExtension; +import org.jivesoftware.smackx.pubsub.PubSubElementType; +import org.jivesoftware.smackx.pubsub.Subscription; +import org.jivesoftware.smackx.pubsub.SubscriptionsExtension; import org.jivesoftware.smackx.pubsub.packet.PubSub; import org.jivesoftware.smackx.pubsub.packet.PubSubNamespace; import org.jivesoftware.smackx.pubsub.packet.SyncPacketSend; @@ -38,7 +47,8 @@ import com.raytheon.uf.common.xmpp.ext.ChangeAffiliationExtension; * * Date Ticket# Engineer Description * ------------ ---------- ----------- -------------------------- - * Feb 18, 2014 2751 bclement Initial creation + * Feb 18, 2014 2751 bclement Initial creation + * Apr 15, 2014 2822 bclement added getAllSubscriptions() * * * @@ -47,11 +57,14 @@ import com.raytheon.uf.common.xmpp.ext.ChangeAffiliationExtension; */ public class PubSubOperations { + public static final String PUBSUB_SUBDOMAIN_PREFIX = "pubsub."; + private PubSubOperations() { } /** - * Send packet to change affiliation of user on a pubsub topic node + * Send packet to change affiliation of user on a pubsub topic node. Calling + * client must be owner of node. * * @param conn * @param affiliation @@ -59,12 +72,56 @@ public class PubSubOperations { */ 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); + PubSub packet = createOwnerPacket(conn, affiliation, IQ.Type.SET); SyncPacketSend.getReply(conn, packet); } + /** + * List all subscriptions on node. Calling client must be owner of node. + * + * @param conn + * @param n + * @return + * @throws XMPPException + */ + public static List getAllSubscriptions(XMPPConnection conn, + Node n) throws XMPPException { + PubSubElementType type = PubSubElementType.SUBSCRIPTIONS; + /* + * we need to use the OWNER namespace when we make the request, but we + * reuse the provider (parser) for the default namespace for the return. + * Use the default namespace to get the extension object from the packet + */ + String namespace = type.getNamespace().getXmlns(); + NodeExtension ext = new NodeExtension(type, n.getId()); + PubSub packet = createOwnerPacket(conn, ext, Type.GET); + Packet reply = SyncPacketSend.getReply(conn, packet); + SubscriptionsExtension resp = (SubscriptionsExtension) reply + .getExtension(type.getElementName(), namespace); + if (resp == null){ + throw new XMPPException( + "Subscriptions response missing content for topic: " + + n.getId()); + } + return resp.getSubscriptions(); + } + + /** + * Create pubsub packet object with owner namespace + * + * @param conn + * @param ext + * @param type + * @return + */ + private static PubSub createOwnerPacket(XMPPConnection conn, + NodeExtension ext, Type type) { + PubSub packet = new PubSub(); + packet.setType(type); + packet.setTo(PUBSUB_SUBDOMAIN_PREFIX + conn.getServiceName()); + packet.setPubSubNamespace(PubSubNamespace.OWNER); + packet.addExtension(ext); + return 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 a54ec03931..72af6271ba 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 @@ -23,7 +23,9 @@ import java.net.URI; import java.util.Collection; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; import org.apache.http.client.methods.HttpDelete; import org.jivesoftware.smack.XMPPConnection; @@ -67,6 +69,7 @@ import com.raytheon.uf.viz.collaboration.comm.provider.account.ClientAuthManager import com.raytheon.uf.viz.collaboration.comm.provider.connection.CollaborationConnection; import com.raytheon.uf.viz.collaboration.comm.provider.connection.PeerToPeerCommHelper; import com.raytheon.uf.viz.collaboration.comm.provider.event.LeaderChangeEvent; +import com.raytheon.uf.viz.collaboration.comm.provider.user.IDConverter; import com.raytheon.uf.viz.collaboration.comm.provider.user.UserId; import com.raytheon.uf.viz.collaboration.comm.provider.user.VenueParticipant; @@ -95,6 +98,7 @@ import com.raytheon.uf.viz.collaboration.comm.provider.user.VenueParticipant; * Mar 07, 2014 2848 bclement moved pubsub close logic to closePubSub() * ensure that subscription is setup before joining room * Mar 31, 2014 2899 mpduff Improve error messages. + * Apr 15, 2014 2822 bclement added check for other participants being subscribed to topic * * * @@ -116,6 +120,8 @@ public class SharedDisplaySession extends VenueSession implements private LeafNode topic; + private final Map topicSubscribers = new ConcurrentHashMap(); + private XMPPConnection conn; private boolean closed = false; @@ -624,6 +630,36 @@ public class SharedDisplaySession extends VenueSession implements PubSubOperations.sendAffiliationPacket(conn, affiliation); } + /** + * @param user + * @return true if user has a subscription to the session topic + * @throws XMPPException + */ + private boolean isSubscribedToTopic(UserId user) + throws XMPPException { + Boolean rval = topicSubscribers.get(user); + if (rval == null) { + synchronized (topicSubscribers) { + List subs = PubSubOperations.getAllSubscriptions( + conn, topic); + for (Subscription sub : subs) { + topicSubscribers.put(IDConverter.convertFrom(sub.getJid()), + true); + } + } + rval = topicSubscribers.get(user); + if (rval == null) { + /* + * userid object hash includes resource, cache as a client that + * doesn't use topic + */ + topicSubscribers.put(user, false); + rval = false; + } + } + return rval; + } + @Override public void changeLeader(VenueParticipant newLeader) throws CollaborationException { @@ -645,6 +681,15 @@ public class SharedDisplaySession extends VenueSession implements boolean othersNotified = false; String revokeTarget = null; try { + /* + * make sure that the new leader is not just in the room, but also + * subscribed to the pubsub topic + */ + if (!isSubscribedToTopic(actualId)) { + throw new CollaborationException( + "Unable to grant ownership because new leader is not subscribed to session topic"); + } + // was formerly the data provider, so hand off pubsub ownership grantTopicOwnership(newLeaderId); topicOwnershipGranted = true; @@ -670,6 +715,8 @@ public class SharedDisplaySession extends VenueSession implements // 'member' instead of just down to 'admin' revokeTarget = "room"; muc.revokeAdmin(account.getNormalizedId()); + // clear cache of topic subscribers + topicSubscribers.clear(); } catch (XMPPException e) { if (!othersNotified) { // transaction, attempt to roll back the ownership changes @@ -713,4 +760,27 @@ public class SharedDisplaySession extends VenueSession implements return closed; } + /* + * (non-Javadoc) + * + * @see + * com.raytheon.uf.viz.collaboration.comm.identity.ISharedDisplaySession + * #isSharedDisplayClient + * (com.raytheon.uf.viz.collaboration.comm.provider.user.VenueParticipant) + */ + @Override + public boolean isSharedDisplayClient(VenueParticipant participant) { + UserId actualId = getVenue().getParticipantUserid(participant); + boolean rval = false; + if (actualId != null) { + try { + rval = isSubscribedToTopic(actualId); + } catch (XMPPException e) { + log.error("Error checking if user is a shared display client", + e); + } + } + return rval; + } + } 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 b7c92ff443..a27ce5e13c 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 @@ -100,6 +100,7 @@ import com.raytheon.viz.ui.input.EditableManager; * Mar 06, 2014 2848 bclement moved colormanager update code to session container * Mar 11, 2014 2865 lvenable Added null checks in threads * Mar 18, 2014 2895 njensen Fix lockAction enable/disable logic + * Apr 15, 2014 2822 bclement only allow transfer leader if participant is using shared display * * * @@ -539,7 +540,8 @@ public class CollaborationSessionView extends SessionView implements .getSelection(); VenueParticipant entry = (VenueParticipant) selection .getFirstElement(); - if (!entry.isSameUser(session.getUserID())) { + if (!entry.isSameUser(session.getUserID()) + && session.isSharedDisplayClient(entry)) { manager.add(leaderChangeAction); } }