Issue #2563 compatibility features for stock xmpp servers

added more listener handling for venue events and server disconnection
added capability to connect to a different server from UI
added better error messages for failed connection/login to server
fixed assumptions that server always sends HTTP url and has feed chatroom


Former-commit-id: b3173727f3f685119be3668779eb5da7e075df50
This commit is contained in:
Brian Clements 2013-12-10 17:00:04 -06:00
parent 54a986613a
commit 09c0267237
16 changed files with 815 additions and 213 deletions

View file

@ -33,6 +33,7 @@ import com.raytheon.uf.viz.collaboration.comm.provider.user.UserId;
* Date Ticket# Engineer Description
* ------------ ---------- ----------- --------------------------
* Mar 20, 2012 jkorman Initial creation
* Dec 19, 2013 2563 bclement added description getter
*
* </pre>
*
@ -56,4 +57,9 @@ public interface IVenueParticipantEvent {
* @return presence of participant, may be null
*/
public Presence getPresence();
/**
* @return description of participant update event, may be null
*/
public String getEventDescription();
}

View file

@ -0,0 +1,57 @@
/**
* 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.event;
/**
* Event sent when the server drops connection with the client
*
* <pre>
*
* SOFTWARE HISTORY
*
* Date Ticket# Engineer Description
* ------------ ---------- ----------- --------------------------
* Dec 19, 2013 2563 bclement Initial creation
*
* </pre>
*
* @author bclement
* @version 1.0
*/
public class ServerDisconnectEvent {
private final String reason;
/**
* @param reason
*/
public ServerDisconnectEvent(String reason) {
this.reason = reason;
}
/**
* @return the reason
*/
public String getReason() {
return reason;
}
}

View file

@ -35,6 +35,7 @@ import com.raytheon.uf.viz.collaboration.comm.provider.user.UserId;
* Date Ticket# Engineer Description
* ------------ ---------- ----------- --------------------------
* Mar 20, 2012 jkorman Initial creation
* Dec 19, 2013 2563 bclement added description
*
* </pre>
*
@ -50,6 +51,8 @@ public class VenueParticipantEvent implements IVenueParticipantEvent {
private Presence presence;
private String eventDescription;
public VenueParticipantEvent(UserId participant,
ParticipantEventType eventType) {
this.participant = participant;
@ -87,4 +90,24 @@ public class VenueParticipantEvent implements IVenueParticipantEvent {
return presence;
}
/*
* (non-Javadoc)
*
* @see
* com.raytheon.uf.viz.collaboration.comm.identity.event.IVenueParticipantEvent
* #getEventDescription()
*/
@Override
public String getEventDescription() {
return eventDescription;
}
/**
* @param eventDescription
* the eventDescription to set
*/
public void setEventDescription(String eventDescription) {
this.eventDescription = eventDescription;
}
}

View file

@ -0,0 +1,58 @@
/**
* 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.event;
/**
* Event sent when actions are taken on the user in the venue (kicked, banned,
* granted/revoked privileges, etc)
*
* <pre>
*
* SOFTWARE HISTORY
*
* Date Ticket# Engineer Description
* ------------ ---------- ----------- --------------------------
* Dec 19, 2013 2563 bclement Initial creation
*
* </pre>
*
* @author bclement
* @version 1.0
*/
public class VenueUserEvent {
private final String message;
/**
* @param message
*/
public VenueUserEvent(String message) {
this.message = message;
}
/**
* @return the message
*/
public String getMessage() {
return message;
}
}

View file

@ -28,6 +28,7 @@ import java.util.Map;
import org.apache.commons.lang.StringUtils;
import org.jivesoftware.smack.Connection;
import org.jivesoftware.smack.ConnectionConfiguration;
import org.jivesoftware.smack.ConnectionListener;
import org.jivesoftware.smack.Roster;
import org.jivesoftware.smack.RosterEntry;
import org.jivesoftware.smack.RosterListener;
@ -38,6 +39,8 @@ import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smack.packet.Presence;
import org.jivesoftware.smack.packet.Presence.Mode;
import org.jivesoftware.smack.packet.Presence.Type;
import org.jivesoftware.smack.packet.StreamError;
import org.jivesoftware.smack.packet.XMPPError;
import org.jivesoftware.smack.provider.ProviderManager;
import org.jivesoftware.smackx.muc.InvitationListener;
import org.jivesoftware.smackx.muc.MultiUserChat;
@ -65,6 +68,7 @@ import com.raytheon.uf.viz.collaboration.comm.provider.SessionPayload.PayloadTyp
import com.raytheon.uf.viz.collaboration.comm.provider.SessionPayloadProvider;
import com.raytheon.uf.viz.collaboration.comm.provider.Tools;
import com.raytheon.uf.viz.collaboration.comm.provider.event.RosterChangeEvent;
import com.raytheon.uf.viz.collaboration.comm.provider.event.ServerDisconnectEvent;
import com.raytheon.uf.viz.collaboration.comm.provider.event.VenueInvitationEvent;
import com.raytheon.uf.viz.collaboration.comm.provider.info.VenueInfo;
import com.raytheon.uf.viz.collaboration.comm.provider.user.ContactsManager;
@ -100,6 +104,8 @@ import com.raytheon.uf.viz.collaboration.comm.provider.user.VenueId;
* Apr 18, 2012 njensen Major cleanup
* Dec 6, 2013 2561 bclement removed ECF
* Dec 18, 2013 2562 bclement added smack compression, fixed invite parsing
* Dec 19, 2013 2563 bclement added connection listener,
* added better error message on failed connection
*
* </pre>
*
@ -180,15 +186,10 @@ public class CollaborationConnection implements IEventPublisher {
this.user = new UserId(connectionData.getUserName(),
connectionData.getServer());
try {
connection.connect();
connection.login(user.getName(), password);
} catch (XMPPException e) {
closeInternals();
throw new CollaborationException("Login failed.", e);
}
connectInternal(user.getName(), password);
setupConnectionListener();
setupAccountManager();
setupInternalConnectionListeners();
setupInternalVenueInvitationListener();
@ -209,6 +210,44 @@ public class CollaborationConnection implements IEventPublisher {
}
}
/**
* connect to XMPP server and login
*
* @param username
* @param password
* @throws CollaborationException
*/
private void connectInternal(String username, String password)
throws CollaborationException {
try {
connection.connect();
connection.login(username, password);
} catch (XMPPException e) {
closeInternals();
// get a nice reason for the user
String msg;
XMPPError xmppErr = e.getXMPPError();
if (xmppErr != null) {
switch (xmppErr.getCode()) {
case 401:
msg = "Bad username or password";
break;
case 403:
msg = "User not allowed to connect to server";
break;
case 409:
msg = "User account already in use by another client";
break;
default:
msg = e.getLocalizedMessage();
}
} else {
msg = e.getLocalizedMessage();
}
throw new CollaborationException("Login failed: " + msg, e);
}
}
public CollaborationConnectionData getConnectionData() {
return connectionData;
}
@ -299,6 +338,7 @@ public class CollaborationConnection implements IEventPublisher {
connection.disconnect();
connection = null;
}
PeerToPeerCommHelper.reset();
instanceMap.remove(connectionData);
if (this == instance) {
instance = null;
@ -405,6 +445,22 @@ public class CollaborationConnection implements IEventPublisher {
}
}
/**
* Check if venue exists on server
*
* @param venueName
* @return false on error
*/
public boolean venueExistsOnServer(String venueName) {
String roomId = VenueSession.getRoomId(connection.getHost(), venueName);
try {
return VenueSession.roomExistsOnServer(connection, roomId);
} catch (XMPPException e) {
statusHandler.error("Unable to check for room on server", e);
return false;
}
}
/**
*
* @param venueName
@ -529,6 +585,65 @@ public class CollaborationConnection implements IEventPublisher {
}
}
private void setupConnectionListener(){
if (isConnected()){
connection.addConnectionListener(new ConnectionListener() {
@Override
public void reconnectionSuccessful() {
statusHandler.debug("Client successfully reconnected to server");
}
@Override
public void reconnectionFailed(Exception e) {
String reason = getErrorReason(e);
statusHandler.error("Client can't reconnect to server: "
+ reason, e);
sendDisconnectNotice(reason);
}
@Override
public void reconnectingIn(int seconds) {
statusHandler.debug("Client reconnecting to server in " + seconds + " seconds" );
}
@Override
public void connectionClosedOnError(Exception e) {
String reason = getErrorReason(e);
statusHandler.error("Server closed on error: " + reason, e);
sendDisconnectNotice(reason);
}
private String getErrorReason(Exception e) {
String msg = null;
if (e instanceof XMPPException) {
StreamError streamError = ((XMPPException) e)
.getStreamError();
if (streamError != null) {
if ("conflict".equalsIgnoreCase(streamError
.getCode())) {
msg = "User account in use on another client";
}
}
}
return msg == null ? e.getLocalizedMessage() : msg;
}
@Override
public void connectionClosed() {
statusHandler.info("Server closed connection");
sendDisconnectNotice("Normal termination");
}
private void sendDisconnectNotice(String reason) {
ServerDisconnectEvent event = new ServerDisconnectEvent(
reason);
eventBus.post(event);
}
});
}
}
// ***************************
// Venue invitation listener management
// ***************************
@ -558,7 +673,8 @@ public class CollaborationConnection implements IEventPublisher {
return;
}
}
if ( reason.startsWith(Tools.CMD_PREAMBLE)){
if (reason != null
&& reason.startsWith(Tools.CMD_PREAMBLE)) {
reason = "Shared display invitation from incompatible version of CAVE. "
+ "Session will be chat-only if invitation is accepted";
}

View file

@ -52,6 +52,7 @@ import com.raytheon.uf.viz.collaboration.comm.provider.user.IDConverter;
* Dec 6, 2013 2561 bclement removed ECF
* Dec 18, 2013 2562 bclement added timeout for HTTP config,
* data now in packet extension
* Dec 19, 2013 2563 bclement removed wait for HTTP config, added reset
*
* </pre>
*
@ -64,41 +65,16 @@ public class PeerToPeerCommHelper implements PacketListener {
private static final transient IUFStatusHandler statusHandler = UFStatus
.getHandler(PeerToPeerCommHelper.class);
private static Object httpServerLockObj = new Object();
private static String httpServer;
private static long HTTP_SERVER_TIMEOUT = 10 * 1000; // 10 seconds
private static volatile String httpServer;
/**
* Get HTTP server address. This method blocks until the address has been
* received from the chat server or a timeout has expired.
* Get HTTP server address. This value will be updated if the server sends
* new HTTP configuration. If this address is null, the server most likely
* doesn't support collaborative displays.
*
* @return
*/
public static String getCollaborationHttpServer() {
/**
* Wait for initialization of field httpServer.
*/
synchronized (httpServerLockObj) {
long start = System.currentTimeMillis();
try {
while (httpServer == null) {
if (System.currentTimeMillis() - start > HTTP_SERVER_TIMEOUT) {
// TODO should we get fallback server address from
// localization?
statusHandler
.error("HTTP URL configuration not received from server");
break;
}
httpServerLockObj.wait(500);
}
} catch (InterruptedException e) {
statusHandler.handle(Priority.PROBLEM,
"PeerToPeerCommHelper unable to resolve server URL. "
+ e.getLocalizedMessage(), e);
}
}
return httpServer;
}
@ -262,10 +238,7 @@ public class PeerToPeerCommHelper implements PacketListener {
return;
}
synchronized (httpServerLockObj) {
httpServer = httpdCollaborationURL;
httpServerLockObj.notifyAll();
}
httpServer = httpdCollaborationURL;
// configuration is valid; publish it.
IHttpdCollaborationConfigurationEvent configurationEvent = new HttpdCollaborationConfigurationEvent(
httpdCollaborationURL);
@ -296,4 +269,11 @@ public class PeerToPeerCommHelper implements PacketListener {
manager.postEvent(configurationEvent);
}
/**
* reset internal state when client disconnects from server
*/
public static void reset() {
httpServer = null;
}
}

View file

@ -34,6 +34,7 @@ import org.jivesoftware.smackx.Form;
import org.jivesoftware.smackx.ServiceDiscoveryManager;
import org.jivesoftware.smackx.muc.MultiUserChat;
import org.jivesoftware.smackx.muc.ParticipantStatusListener;
import org.jivesoftware.smackx.muc.UserStatusListener;
import org.jivesoftware.smackx.packet.DiscoverItems;
import com.google.common.eventbus.EventBus;
@ -53,7 +54,9 @@ import com.raytheon.uf.viz.collaboration.comm.provider.SessionPayload;
import com.raytheon.uf.viz.collaboration.comm.provider.SessionPayload.PayloadType;
import com.raytheon.uf.viz.collaboration.comm.provider.TextMessage;
import com.raytheon.uf.viz.collaboration.comm.provider.Tools;
import com.raytheon.uf.viz.collaboration.comm.provider.event.UserNicknameChangedEvent;
import com.raytheon.uf.viz.collaboration.comm.provider.event.VenueParticipantEvent;
import com.raytheon.uf.viz.collaboration.comm.provider.event.VenueUserEvent;
import com.raytheon.uf.viz.collaboration.comm.provider.info.Venue;
import com.raytheon.uf.viz.collaboration.comm.provider.user.IDConverter;
import com.raytheon.uf.viz.collaboration.comm.provider.user.UserId;
@ -83,6 +86,7 @@ import com.raytheon.uf.viz.collaboration.comm.provider.user.UserId;
* Apr 17, 2012 njensen Major refactor
* Dec 6, 2013 2561 bclement removed ECF
* Dec 18, 2013 2562 bclement moved data to packet extension
* Dec 19, 2013 2563 bclement status listeners now send all events to bus
*
* </pre>
*
@ -265,7 +269,7 @@ public class VenueSession extends BaseSession implements IVenueSession {
* @param roomName
* @return
*/
private String getRoomId(String host, String roomName) {
public static String getRoomId(String host, String roomName) {
return roomName + "@conference." + host;
}
@ -284,7 +288,7 @@ public class VenueSession extends BaseSession implements IVenueSession {
CollaborationConnection manager = getSessionManager();
XMPPConnection conn = manager.getXmppConnection();
String roomId = getRoomId(conn.getHost(), venueName);
if (roomExistsOnServer(roomId)) {
if (roomExistsOnServer(conn, roomId)) {
throw new CollaborationException("Session name already in use");
}
this.muc = new MultiUserChat(conn, roomId);
@ -307,10 +311,9 @@ public class VenueSession extends BaseSession implements IVenueSession {
* @return true if room exists on server
* @throws XMPPException
*/
protected boolean roomExistsOnServer(String roomId) throws XMPPException {
public static boolean roomExistsOnServer(XMPPConnection conn, String roomId)
throws XMPPException {
String host = Tools.parseHost(roomId);
CollaborationConnection manager = getSessionManager();
XMPPConnection conn = manager.getXmppConnection();
ServiceDiscoveryManager serviceDiscoveryManager = new ServiceDiscoveryManager(
conn);
DiscoverItems result = serviceDiscoveryManager.discoverItems(host);
@ -333,95 +336,105 @@ public class VenueSession extends BaseSession implements IVenueSession {
@Override
public void voiceRevoked(String participant) {
// TODO Auto-generated method stub
sendParticipantEvent(participant, ParticipantEventType.UPDATED,
"is no longer allowed to chat.");
}
@Override
public void voiceGranted(String participant) {
// TODO Auto-generated method stub
sendParticipantEvent(participant, ParticipantEventType.UPDATED,
"is now allowed to chat.");
}
@Override
public void ownershipRevoked(String participant) {
// TODO Auto-generated method stub
sendParticipantEvent(participant, ParticipantEventType.UPDATED,
"is no longer a room owner.");
}
@Override
public void ownershipGranted(String participant) {
// TODO Auto-generated method stub
sendParticipantEvent(participant, ParticipantEventType.UPDATED,
"is now a room owner.");
}
@Override
public void nicknameChanged(String participant, String newNickname) {
// TODO how do we pass along new nickname?
UserId user = IDConverter.convertFromRoom(muc, participant);
postEvent(new VenueParticipantEvent(user,
ParticipantEventType.UPDATED));
postEvent(new UserNicknameChangedEvent(user, newNickname));
}
@Override
public void moderatorRevoked(String participant) {
// TODO Auto-generated method stub
sendParticipantEvent(participant, ParticipantEventType.UPDATED,
"is no longer a moderator.");
}
@Override
public void moderatorGranted(String participant) {
// TODO Auto-generated method stub
sendParticipantEvent(participant, ParticipantEventType.UPDATED,
"is now a moderator.");
}
@Override
public void membershipRevoked(String participant) {
// TODO Auto-generated method stub
sendParticipantEvent(participant, ParticipantEventType.UPDATED,
"is no longer a member of the room.");
}
@Override
public void membershipGranted(String participant) {
// TODO Auto-generated method stub
sendParticipantEvent(participant, ParticipantEventType.UPDATED,
"is now a member of the room.");
}
@Override
public void left(String participant) {
UserId user = IDConverter.convertFromRoom(muc, participant);
postEvent(new VenueParticipantEvent(user,
ParticipantEventType.DEPARTED));
sendParticipantEvent(participant,
ParticipantEventType.DEPARTED, "has left the room.");
}
@Override
public void kicked(String participant, String actor, String reason) {
this.left(participant);
// no period since formatter adds it
sendParticipantEvent(participant,
ParticipantEventType.DEPARTED,
formatEjectionString("has been kicked", actor, reason));
}
@Override
public void joined(String participant) {
UserId user = IDConverter.convertFromRoom(muc, participant);
postEvent(new VenueParticipantEvent(user,
ParticipantEventType.ARRIVED));
sendParticipantEvent(participant, ParticipantEventType.ARRIVED,
"has entered the room.");
}
@Override
public void banned(String participant, String actor, String reason) {
this.left(participant);
// no period since formatter adds it
sendParticipantEvent(participant,
ParticipantEventType.DEPARTED,
formatEjectionString("has been banned", actor, reason));
}
@Override
public void adminRevoked(String participant) {
// TODO Auto-generated method stub
sendParticipantEvent(participant, ParticipantEventType.UPDATED,
"is no longer an admin.");
}
@Override
public void adminGranted(String participant) {
// TODO Auto-generated method stub
sendParticipantEvent(participant, ParticipantEventType.UPDATED,
"is now an admin.");
}
private void sendParticipantEvent(String participant,
ParticipantEventType type, String desciption) {
UserId user = IDConverter.convertFromRoom(muc, participant);
VenueParticipantEvent event = new VenueParticipantEvent(user,
ParticipantEventType.ARRIVED);
event.setEventDescription(desciption);
postEvent(event);
}
});
@ -456,6 +469,103 @@ public class VenueSession extends BaseSession implements IVenueSession {
}
});
// listens for our own status changes
this.muc.addUserStatusListener(new UserStatusListener() {
@Override
public void voiceRevoked() {
sendUserEvent("Your chat privileges have been revoked.");
}
@Override
public void voiceGranted() {
sendUserEvent("Your chat privileges have been granted.");
}
@Override
public void ownershipRevoked() {
sendUserEvent("You are no longer an owner of this room.");
}
@Override
public void ownershipGranted() {
sendUserEvent("You are now an owner of this room.");
}
@Override
public void moderatorRevoked() {
sendUserEvent("You are no longer a moderator of this room.");
}
@Override
public void moderatorGranted() {
sendUserEvent("You are now the moderator of this room.");
}
@Override
public void membershipRevoked() {
sendUserEvent("You are no longer a member of this room.");
}
@Override
public void membershipGranted() {
sendUserEvent("You are now a member of this room.");
}
@Override
public void kicked(String actor, String reason) {
// no period since formatter adds it
sendUserEvent(formatEjectionString("You have had been kicked",
actor, reason));
// TODO disable window?
}
@Override
public void banned(String actor, String reason) {
// no period since formatter adds it
sendUserEvent(formatEjectionString("You have been banned",
actor, reason));
// TODO disable window?
}
@Override
public void adminRevoked() {
sendUserEvent("You have had admin privileges revoked.");
}
@Override
public void adminGranted() {
sendUserEvent("You have had admin privileges granted.");
}
private void sendUserEvent(String message) {
postEvent(new VenueUserEvent(message));
}
});
}
/**
* Format reason for being kicked/banned from venue. Actor and reason will
* be appended to base if not null or empty. Formatter will add period at
* end of string.
*
* @param base
* @param actor
* @param reason
* @return
*/
private String formatEjectionString(String base, String actor, String reason) {
StringBuilder rval = new StringBuilder(base);
if (!StringUtils.isBlank(actor)) {
rval.append(" by ").append(actor);
}
if (!StringUtils.isBlank(reason)) {
rval.append(" with reason '").append(reason).append("'");
} else {
rval.append(" with no reason given");
}
rval.append(".");
return rval.toString();
}
/**
@ -563,10 +673,10 @@ public class VenueSession extends BaseSession implements IVenueSession {
}
/**
* Convert from an ECF chat room message to an IMessage instance.
* Convert from an chat room message to an IMessage instance.
*
* @param msg
* The ECF chat room message to convert.
* The chat room message to convert.
* @return The converted message.
*/
private IMessage convertMessage(Message msg) {

View file

@ -83,6 +83,7 @@ import org.osgi.framework.Bundle;
import com.google.common.eventbus.Subscribe;
import com.raytheon.uf.viz.collaboration.comm.identity.IVenueSession;
import com.raytheon.uf.viz.collaboration.comm.identity.event.IRosterChangeEvent;
import com.raytheon.uf.viz.collaboration.comm.provider.event.ServerDisconnectEvent;
import com.raytheon.uf.viz.collaboration.comm.provider.event.UserNicknameChangedEvent;
import com.raytheon.uf.viz.collaboration.comm.provider.event.UserPresenceChangedEvent;
import com.raytheon.uf.viz.collaboration.comm.provider.session.CollaborationConnection;
@ -132,6 +133,7 @@ import com.raytheon.viz.ui.views.CaveFloatingView;
* Mar 1, 2012 rferrel Initial creation
* Oct 22, 2013 #2483 lvenable Fixed image memory leak.
* Dec 6, 2013 2561 bclement removed ECF
* Dec 19, 2013 2563 bclement added subscribe method for server disconnection
*
* </pre>
*
@ -166,6 +168,8 @@ public class CollaborationGroupView extends CaveFloatingView implements
private Image pressedImage = null;
private LogoutAction logOut;
/**
* @param parent
*/
@ -320,7 +324,8 @@ public class CollaborationGroupView extends CaveFloatingView implements
mgr.add(new Separator());
if (CollaborationConnection.getConnection() != null) {
mgr.add(new LogoutAction());
logOut = new LogoutAction();
mgr.add(logOut);
} else {
mgr.add(new LoginAction());
}
@ -876,4 +881,18 @@ public class CollaborationGroupView extends CaveFloatingView implements
public void userNicknameChanged(UserNicknameChangedEvent e) {
refreshUsersTreeViewerAsync(usersTreeViewer.getInput());
}
@Subscribe
public void serverDisconnected(final ServerDisconnectEvent e) {
if (logOut == null) {
// we aren't logged in
return;
}
VizApp.runAsync(new Runnable() {
@Override
public void run() {
logOut.closeCollaboration();
}
});
}
}

View file

@ -57,6 +57,7 @@ import com.raytheon.uf.viz.collaboration.comm.identity.info.IVenueInfo;
import com.raytheon.uf.viz.collaboration.comm.identity.user.SharedDisplayRole;
import com.raytheon.uf.viz.collaboration.comm.provider.Tools;
import com.raytheon.uf.viz.collaboration.comm.provider.session.CollaborationConnection;
import com.raytheon.uf.viz.collaboration.comm.provider.session.PeerToPeerCommHelper;
import com.raytheon.uf.viz.collaboration.display.data.SharedDisplaySessionMgr;
import com.raytheon.uf.viz.collaboration.display.roles.dataprovider.ISharedEditorsManagerListener;
import com.raytheon.uf.viz.collaboration.display.roles.dataprovider.SharedEditorsManager;
@ -81,6 +82,7 @@ import com.raytheon.viz.ui.editor.IMultiPaneEditor;
* Date Ticket# Engineer Description
* ------------ ---------- ----------- --------------------------
* Feb 15, 2012 rferrel Initial creation
* Dec 19, 2013 2563 bclement disable shared display option if not supported by server
*
* </pre>
*
@ -265,31 +267,24 @@ public class CreateSessionDialog extends CaveSWTDialog {
IEditorPart editor = EditorUtil.getActiveEditorAs(IEditorPart.class);
if (!sharedSessionDisplay.isDisposed()) {
if (editor instanceof CollaborationEditor) {
sharedSessionDisplay
.setText("Create Shared Display Session *Client Session*");
sharedSessionDisplay.setEnabled(false);
sharedSessionDisplay.setSelection(false);
sharedSessionDisplay.getParent().setToolTipText(
if (!serverSupportsSharing()) {
disableShareOption(
"Not Supported By Server",
"Unable to create a shared display session because"
+ " the server doesn't support shared display sessions.");
} else if (editor instanceof CollaborationEditor) {
disableShareOption("Client Session",
"Unable to create a shared display session because"
+ " the active editor is a client session.");
} else if (!isShareable(editor)) {
sharedSessionDisplay
.setText("Create Shared Display Session *Not Shareable*");
sharedSessionDisplay.setEnabled(false);
sharedSessionDisplay.setSelection(false);
sharedSessionDisplay.getParent().setToolTipText(
disableShareOption("Not Shareable",
"Unable to create a shared display session because"
+ " the active editor is not shareable.");
} else if (editor != null
&& editor instanceof AbstractEditor
&& SharedEditorsManager
.isBeingShared((AbstractEditor) editor)) {
sharedSessionDisplay
.setText("Create Shared Display Session *Already Shared*");
sharedSessionDisplay.setEnabled(false);
sharedSessionDisplay.setSelection(false);
sharedSessionDisplay.getParent().setToolTipText(
disableShareOption("Already Shared",
"Unable to create a shared display session because"
+ " the active editor is already "
+ "in a shared display session.");
@ -301,6 +296,28 @@ public class CreateSessionDialog extends CaveSWTDialog {
}
}
/**
* Disable create shared display checkbox
*
* @param shortReason
* @param longReason
*/
private void disableShareOption(String shortReason, String longReason) {
String text = String.format("Create Shared Display Session *%s*",
shortReason);
sharedSessionDisplay.setText(text);
sharedSessionDisplay.setEnabled(false);
sharedSessionDisplay.setSelection(false);
sharedSessionDisplay.getParent().setToolTipText(longReason);
}
/**
* @return true if the server supports shared display sessions
*/
private boolean serverSupportsSharing() {
return PeerToPeerCommHelper.getCollaborationHttpServer() != null;
}
@Override
protected void disposed() {
super.disposed();

View file

@ -53,6 +53,7 @@ import com.raytheon.viz.ui.views.CaveWorkbenchPageManager;
* Date Ticket# Engineer Description
* ------------ ---------- ----------- --------------------------
* Jul 5, 2012 bsteffen Initial creation
* Dec 19, 2013 2563 bclement added check for feed venue existence
*
* </pre>
*
@ -113,6 +114,14 @@ public class DisplayFeedAction extends Action {
@Override
public void run() {
CollaborationConnection connection = CollaborationConnection
.getConnection();
if (!connection.venueExistsOnServer(FEED_VENUE)) {
statusHandler.info("Feed venue doesn't exist on server: "
+ FEED_VENUE);
return;
}
// handle if it is clicked to close or open the view as
// necessary
CaveWorkbenchPageManager page = CaveWorkbenchPageManager

View file

@ -53,6 +53,7 @@ import com.raytheon.viz.ui.views.CaveWorkbenchPageManager;
* Date Ticket# Engineer Description
* ------------ ---------- ----------- --------------------------
* Jul 11, 2012 bsteffen Initial creation
* Dec 19, 2013 2563 bclement moved close logic to public method
*
* </pre>
*
@ -81,35 +82,44 @@ public class LogoutAction extends Action {
+ "close all collaboration views\n" + "and editors.");
int result = messageBox.open();
if (result == SWT.OK) {
for (IViewReference ref : CaveWorkbenchPageManager
.getActiveInstance().getViewReferences()) {
IViewPart view = ref.getView(false);
if (view instanceof AbstractSessionView
|| view instanceof CollaborationGroupView) {
CaveWorkbenchPageManager.getActiveInstance().hideView(ref);
}
}
// Close all Collaboration Editors.
for (IEditorReference ref : PlatformUI.getWorkbench()
.getActiveWorkbenchWindow().getActivePage()
.getEditorReferences()) {
IEditorPart editor = ref.getEditor(false);
if (editor instanceof CollaborationEditor) {
PlatformUI.getWorkbench().getActiveWorkbenchWindow()
.getActivePage().hideEditor(ref);
}
}
try {
Activator.getDefault().getPreferenceStore().save();
} catch (IOException e) {
statusHandler.handle(Priority.WARN,
"Unable to save preferences", e);
}
CollaborationConnection connection = CollaborationConnection
.getConnection();
ConnectionSubscriber.unsubscribe(connection);
connection.close();
closeCollaboration();
}
}
/**
* Close collaboration UI and close connection
*
*/
public void closeCollaboration() {
for (IViewReference ref : CaveWorkbenchPageManager.getActiveInstance()
.getViewReferences()) {
IViewPart view = ref.getView(false);
if (view instanceof AbstractSessionView
|| view instanceof CollaborationGroupView) {
CaveWorkbenchPageManager.getActiveInstance().hideView(ref);
}
}
// Close all Collaboration Editors.
for (IEditorReference ref : PlatformUI.getWorkbench()
.getActiveWorkbenchWindow().getActivePage()
.getEditorReferences()) {
IEditorPart editor = ref.getEditor(false);
if (editor instanceof CollaborationEditor) {
PlatformUI.getWorkbench().getActiveWorkbenchWindow()
.getActivePage().hideEditor(ref);
}
}
try {
Activator.getDefault().getPreferenceStore().save();
} catch (IOException e) {
statusHandler
.handle(Priority.WARN, "Unable to save preferences", e);
}
CollaborationConnection connection = CollaborationConnection
.getConnection();
ConnectionSubscriber.unsubscribe(connection);
connection.close();
}
}

View file

@ -65,6 +65,7 @@ import com.raytheon.uf.viz.collaboration.ui.prefs.CollabPrefConstants;
* Date Ticket# Engineer Description
* ------------ ---------- ----------- --------------------------
* Jun 18, 2012 mschenke Initial creation
* Dec 19, 2013 2563 bclement added option to connect to server not in list
*
* </pre>
*
@ -74,9 +75,7 @@ import com.raytheon.uf.viz.collaboration.ui.prefs.CollabPrefConstants;
public class LoginDialog extends Dialog {
private static final String SERVER_ENABLED = "OK";
private static final String SERVER_DISABLED = "Edit";
private static final String OTHER_SERVER_OPTION = "Other server...";
private IPreferenceStore preferences;
@ -156,29 +155,25 @@ public class LoginDialog extends Dialog {
// retrieve the servers
SiteConfigInformation information = SiteConfigurationManager
.getSiteConfigInformation();
if (information.getServer() == null
|| information.getServer().size() == 0) {
String[] text = new String[1];
text[0] = "Server not configured.";
serverText.setData("configured", false);
serverText.setItems(text);
serverText.setText(text[0]);
} else {
// put configured as true so we don't disable the login button
serverText.setData("configured", true);
List<HostConfig> servers = information.getServer();
String[] names = new String[servers.size()];
int index = 0;
for (int i = 0; i < names.length; i++) {
names[i] = servers.get(i).getPrettyName() + " : "
+ servers.get(i).getHostname();
if (loginData.getServer().equals(names[i])) {
index = i;
}
}
serverText.setItems(names);
serverText.select(index);
List<HostConfig> servers = information.getServer();
if (servers == null) {
servers = new ArrayList<SiteConfigInformation.HostConfig>(0);
}
// put configured as true so we don't disable the login button
serverText.setData("configured", true);
String[] names = new String[servers.size() + 1];
names[0] = OTHER_SERVER_OPTION;
int index = 1;
for (int i = 1; i < names.length; i++) {
HostConfig config = servers.get(i - 1);
names[i] = config.getPrettyName() + " : " + config.getHostname();
if (loginData.getServer().equals(names[i])) {
index = i;
}
}
serverText.setItems(names);
serverText.select(index);
serverText.addListener(SWT.Selection, new ServerInput(serverText, 0));
// Password setting
new Label(body, SWT.NONE).setText("Password: ");
@ -312,10 +307,20 @@ public class LoginDialog extends Dialog {
loginData
.setUserName(loginData.getUserName().toLowerCase());
}
if (loginData.getServer().isEmpty()) {
String server = loginData.getServer();
if (server.isEmpty()) {
errorMessages.add("Must have a server.");
}
String error = ServerInput.validate(server);
if (error != null) {
errorMessages.add(error);
} else {
try {
loginData.setServer(ServerInput.getFullName(server));
} catch (CollaborationException e) {
errorMessages.add(e.getLocalizedMessage());
}
}
if (loginData.getPassword().isEmpty()) {
errorMessages.add("Must enter a password.");

View file

@ -0,0 +1,161 @@
/**
* 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.ui.login;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketAddress;
import java.util.Arrays;
import org.eclipse.jface.dialogs.InputDialog;
import org.eclipse.jface.window.Window;
import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Listener;
import com.google.common.net.HostAndPort;
import com.raytheon.uf.viz.collaboration.comm.identity.CollaborationException;
/**
* Listens for add server option in a Combo box, gets new input from user and
* appends to the combo box
*
* <pre>
*
* SOFTWARE HISTORY
*
* Date Ticket# Engineer Description
* ------------ ---------- ----------- --------------------------
* Dec 18, 2013 2563 bclement Initial creation
*
* </pre>
*
* @author bclement
* @version 1.0
*/
public class ServerInput implements Listener {
private final Combo serverText;
private final int addItemIndex;
private static final int DEFAULT_XMPP_PORT = 5432;
private static final int TIMEOUT = 5000; // 5 seconds
/**
* @param serverText
*/
public ServerInput(Combo serverText, int addItemIndex) {
this.serverText = serverText;
this.addItemIndex = addItemIndex;
}
/*
* (non-Javadoc)
*
* @see
* org.eclipse.swt.widgets.Listener#handleEvent(org.eclipse.swt.widgets.
* Event)
*/
@Override
public void handleEvent(Event event) {
int index = serverText.getSelectionIndex();
if (index == addItemIndex) {
InputDialog dlg = new InputDialog(Display.getCurrent()
.getActiveShell(), "", "Enter server address", "", null);
int status = dlg.open();
if (status == Window.OK) {
String value = dlg.getValue();
addToOptions(value);
}
}
}
/**
* Append new server to options combo and select it
*
* @param server
*/
private void addToOptions(String server) {
String[] items = serverText.getItems();
items = Arrays.copyOf(items, items.length + 1);
items[items.length - 1] = "Custom Server : " + server;
serverText.setItems(items);
serverText.select(items.length - 1);
// this doesn't persist the new server. I can
// see that being a good thing or an annoyance.
}
/**
* Validate server by parsing the string and attempting to ping the server.
* Returns an error string if server string could not be parsed or server
* could not be contacted.
*
* @param server
* @return null if there were no issues
*/
public static String validate(String server) {
Socket s = null;
try {
HostAndPort hnp = HostAndPort.fromString(server).withDefaultPort(
DEFAULT_XMPP_PORT);
s = new Socket();
s.setReuseAddress(true);
SocketAddress sa = new InetSocketAddress(hnp.getHostText(),
hnp.getPort());
s.connect(sa, TIMEOUT);
return null;
} catch (Exception e) {
return "Unable to connect to server: " + e.getLocalizedMessage();
} finally {
if (s != null) {
try {
s.close();
} catch (IOException e) {
// no action
}
}
}
}
/**
* Attempt to get canonical host name for server
*
* @param server
* @return
* @throws CollaborationException
*/
public static String getFullName(String server)
throws CollaborationException {
try {
String hostText = HostAndPort.fromString(server).getHostText();
return InetAddress.getByName(hostText).getCanonicalHostName();
} catch (Exception e) {
throw new CollaborationException("Unable to get full host name", e);
}
}
}

View file

@ -92,6 +92,7 @@ import com.raytheon.viz.ui.views.CaveFloatingView;
* Date Ticket# Engineer Description
* ------------ ---------- ----------- --------------------------
* Mar 16, 2012 244 rferrel Initial creation
* Dec 19, 2013 2563 bclement moved color lookup into runAsync block
*
* </pre>
*
@ -585,35 +586,54 @@ public abstract class AbstractSessionView extends CaveFloatingView {
protected abstract SessionMsgArchive createMessageArchive();
/**
* display formatted error message on chat window
*
* @param sb
* builder containing message
*/
protected void sendErrorMessage(StringBuilder sb) {
Color color = Display.getCurrent().getSystemColor(SWT.COLOR_RED);
sendGenericMessage(sb, color);
sendGenericMessage(sb, SWT.COLOR_RED);
}
/**
* display formatted error message on chat window
*
* @param sb
* builder containing message
*/
protected void sendSystemMessage(StringBuilder sb) {
Color color = Display.getCurrent().getSystemColor(SWT.COLOR_BLACK);
sendGenericMessage(sb, color);
sendGenericMessage(sb, SWT.COLOR_BLACK);
}
private void sendGenericMessage(final StringBuilder string,
final Color color) {
/**
* display formatted error message on chat window
*
* @param builder
* builder containing message
* @param swtColor
* text color for message
*/
private void sendGenericMessage(final StringBuilder builder,
final int swtColor) {
VizApp.runAsync(new Runnable() {
@Override
public void run() {
Color color = Display.getCurrent().getSystemColor(swtColor);
Date date = new Date();
String time = dateFormatter.format(date);
string.insert(0, "(" + time + ") : ");
builder.insert(0, "(" + time + ") : ");
if (messagesText.getCharCount() != 0) {
string.insert(0, "\n");
builder.insert(0, "\n");
}
StyleRange range = new StyleRange(messagesText.getCharCount(),
string.length(), color, null, SWT.BOLD);
builder.length(), color, null, SWT.BOLD);
List<StyleRange> ranges = new ArrayList<StyleRange>();
ranges.add(range);
styleAndAppendText(string, 0, string.toString(), null, ranges,
color);
msgArchive.archiveLine(string.toString());
searchComp.appendText(string.toString());
styleAndAppendText(builder, 0, builder.toString(), null,
ranges, color);
msgArchive.archiveLine(builder.toString());
searchComp.appendText(builder.toString());
}
});
}

View file

@ -59,6 +59,7 @@ import com.raytheon.uf.viz.collaboration.ui.prefs.CollabPrefConstants;
* ------------ ---------- ----------- --------------------------
* Jun 7, 2012 mnash Initial creation
* Dec 6, 2013 2561 bclement removed ECF
* Dec 19, 2013 2563 bclement moved participant filter logic to one method
*
* </pre>
*
@ -357,46 +358,21 @@ public class SessionFeedView extends SessionView {
/*
* (non-Javadoc)
*
* @see
* com.raytheon.uf.viz.collaboration.ui.session.SessionView#participantArrived
* (com.raytheon.uf.viz.collaboration.comm.provider.user.UserId)
* @see com.raytheon.uf.viz.collaboration.ui.session.SessionView#
* sendParticipantSystemMessage
* (com.raytheon.uf.viz.collaboration.comm.provider.user.UserId,
* java.lang.String)
*/
@Override
protected void participantArrived(UserId participant) {
setColorForSite(participant);
if (session != null && session.getVenue() != null) {
Object siteOb = session.getVenue().getPresence(participant)
.getProperty(SiteConfigInformation.SITE_NAME);
String site = "";
if (siteOb != null) {
site = siteOb.toString();
}
// only show sites you care about, or empty site
if ("".equals(site) || enabledSites.contains(site)
|| userEnabledSites.contains(site)) {
super.participantArrived(participant);
} else {
usersTable.setInput(session.getVenue().getParticipants());
usersTable.refresh();
}
}
}
/*
* (non-Javadoc)
*
* @see
* com.raytheon.uf.viz.collaboration.ui.session.SessionView#participantDeparted
* (com.raytheon.uf.viz.collaboration.comm.provider.user.UserId)
*/
@Override
protected void participantDeparted(UserId participant) {
protected void sendParticipantSystemMessage(UserId participant,
String message) {
Presence presence = session.getVenue().getPresence(participant);
Object siteObj = presence.getProperty(SiteConfigInformation.SITE_NAME);
String siteName = siteObj == null ? "" : siteObj.toString();
// only show sites you care about
if (enabledSites.contains(siteName) || userEnabledSites.contains(siteName)) {
super.participantDeparted(participant);
if (enabledSites.contains(siteName)
|| userEnabledSites.contains(siteName)) {
super.sendParticipantSystemMessage(participant, message);
} else {
usersTable.setInput(session.getVenue().getParticipants());
usersTable.refresh();

View file

@ -77,6 +77,7 @@ import com.raytheon.uf.viz.collaboration.comm.identity.event.IVenueParticipantEv
import com.raytheon.uf.viz.collaboration.comm.identity.event.ParticipantEventType;
import com.raytheon.uf.viz.collaboration.comm.identity.info.IVenueInfo;
import com.raytheon.uf.viz.collaboration.comm.provider.event.UserNicknameChangedEvent;
import com.raytheon.uf.viz.collaboration.comm.provider.event.VenueUserEvent;
import com.raytheon.uf.viz.collaboration.comm.provider.session.CollaborationConnection;
import com.raytheon.uf.viz.collaboration.comm.provider.session.VenueSession;
import com.raytheon.uf.viz.collaboration.comm.provider.user.UserId;
@ -98,6 +99,7 @@ import com.raytheon.viz.ui.views.CaveWorkbenchPageManager;
* ------------ ---------- ----------- --------------------------
* Mar 1, 2012 rferrel Initial creation
* Dec 6, 2013 2561 bclement removed ECF
* Dec 19, 2013 2563 bclement reworked participant event logic
*
* </pre>
*
@ -636,6 +638,11 @@ public class SessionView extends AbstractSessionView implements IPrintableView {
}
@Subscribe
public void userEventHandler(VenueUserEvent event) {
sendSystemMessage(new StringBuilder(event.getMessage()));
}
@Subscribe
public void participantHandler(IVenueParticipantEvent event)
throws Exception {
@ -643,6 +650,7 @@ public class SessionView extends AbstractSessionView implements IPrintableView {
final ParticipantEventType type = event.getEventType();
final Presence presence = event.getPresence();
final UserId participant = event.getParticipant();
final String description = event.getEventDescription();
VizApp.runAsync(new Runnable() {
@Override
@ -653,16 +661,19 @@ public class SessionView extends AbstractSessionView implements IPrintableView {
}
switch (type) {
case ARRIVED:
participantArrived(participant);
participantArrived(participant, description);
break;
case DEPARTED:
participantDeparted(participant);
participantDeparted(participant, description);
break;
case PRESENCE_UPDATED:
participantPresenceUpdated(participant, presence);
break;
case UPDATED:
// TODO ?
usersTable.refresh();
if (description != null) {
sendParticipantSystemMessage(participant, description);
}
break;
default:
System.err.println("Unknown Event type");
@ -673,31 +684,55 @@ public class SessionView extends AbstractSessionView implements IPrintableView {
@Subscribe
public void userNicknameChanged(UserNicknameChangedEvent e) {
usersTable.setInput(session.getVenue().getParticipants());
usersTable.refresh();
}
protected void participantArrived(UserId participant) {
/**
* Update participant list and notify user that new participant joined chat
*
* @param participant
*/
protected void participantArrived(UserId participant, String description) {
usersTable.setInput(session.getVenue().getParticipants());
usersTable.refresh();
String name = CollaborationConnection.getConnection()
.getContactsManager().getDisplayName(participant);
StringBuilder builder = new StringBuilder(name
+ " has entered the room");
sendSystemMessage(builder);
String message = description != null ? description
: "has entered the room.";
sendParticipantSystemMessage(participant, message);
}
protected void participantDeparted(UserId participant) {
/**
* Update participant list and notify user that new participant left chat
*
* @param participant
*/
protected void participantDeparted(UserId participant, String description) {
usersTable.setInput(session.getVenue().getParticipants());
usersTable.refresh();
String message = description != null ? description
: "has left the room.";
sendParticipantSystemMessage(participant, message);
}
/**
* Send message about about participant. Message is in the form of a
* statement pertaining to the participant. For example, to get the output
* "Susan was kicked", you would provide Susan's UserId and the message
* "was kicked".
*
* @param participant
* @param message
*/
protected void sendParticipantSystemMessage(UserId participant,
String message) {
CollaborationConnection connection = CollaborationConnection
.getConnection();
if (connection != null) {
String name = connection.getContactsManager().getDisplayName(
participant);
StringBuilder builder = new StringBuilder(name
+ " has left the room");
StringBuilder builder = new StringBuilder(name);
builder.append(" ").append(message);
sendSystemMessage(builder);
}
}