/*
* $Id$
*
* Copyright (c) 2000-2013 by Rodney Kinney, Brent Easton
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License (LGPL) as published by the Free Software Foundation.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, copies are available
* at http://www.opensource.org.
*/
package VASSAL.chat.jabber;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import org.jivesoftware.smack.Chat;
import org.jivesoftware.smack.ConnectionConfiguration;
import org.jivesoftware.smack.PacketListener;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.filter.AndFilter;
import org.jivesoftware.smack.filter.FromContainsFilter;
import org.jivesoftware.smack.filter.IQTypeFilter;
import org.jivesoftware.smack.filter.MessageTypeFilter;
import org.jivesoftware.smack.filter.PacketFilter;
import org.jivesoftware.smack.filter.PacketTypeFilter;
import org.jivesoftware.smack.packet.IQ;
import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smack.packet.Packet;
import org.jivesoftware.smack.packet.PacketExtension;
import org.jivesoftware.smack.packet.Presence;
import org.jivesoftware.smack.packet.XMPPError;
import org.jivesoftware.smack.util.StringUtils;
import org.jivesoftware.smackx.Form;
import org.jivesoftware.smackx.muc.DefaultParticipantStatusListener;
import org.jivesoftware.smackx.muc.DefaultUserStatusListener;
import org.jivesoftware.smackx.muc.HostedRoom;
import org.jivesoftware.smackx.muc.InvitationListener;
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 org.jivesoftware.smackx.packet.MUCUser;
import org.jivesoftware.smackx.packet.VCard;
import VASSAL.build.GameModule;
import VASSAL.chat.LockableChatServerConnection;
import VASSAL.chat.LockableRoom;
import VASSAL.chat.Player;
import VASSAL.chat.PlayerEncoder;
import VASSAL.chat.PrivateChatEncoder;
import VASSAL.chat.PrivateChatManager;
import VASSAL.chat.Room;
import VASSAL.chat.ServerStatus;
import VASSAL.chat.SimpleStatus;
import VASSAL.chat.SoundEncoder;
import VASSAL.chat.SynchEncoder;
import VASSAL.chat.messageboard.MessageBoard;
import VASSAL.chat.ui.ChatControlsInitializer;
import VASSAL.chat.ui.ChatServerControls;
import VASSAL.chat.ui.InviteAction;
import VASSAL.chat.ui.KickAction;
import VASSAL.chat.ui.LockableRoomTreeRenderer;
import VASSAL.chat.ui.PrivateMessageAction;
import VASSAL.chat.ui.RoomInteractionControlsInitializer;
import VASSAL.chat.ui.SendSoundAction;
import VASSAL.chat.ui.ShowProfileAction;
import VASSAL.chat.ui.SimpleStatusControlsInitializer;
import VASSAL.chat.ui.SynchAction;
import VASSAL.command.Command;
import VASSAL.command.CommandEncoder;
import VASSAL.i18n.Resources;
import VASSAL.tools.ErrorDialog;
import VASSAL.tools.io.DeobfuscatingInputStream;
import VASSAL.tools.io.FastByteArrayOutputStream;
import VASSAL.tools.io.IOUtils;
import VASSAL.tools.io.ObfuscatingOutputStream;
import VASSAL.tools.swing.Dialogs;
public class JabberClient implements LockableChatServerConnection, PacketListener, ServerStatus, ChatControlsInitializer, PlayerEncoder {
private static final String QUERY_ROOMS = "http://jabber.org/protocol/muc#rooms"; //$NON-NLS-1$
private static final String QUERY_USER = "http://jabber.org/protocol/muc#user"; //$NON-NLS-1$
private static final String INVITE = "Invite"; //$NON-NLS-1$
private static final String REAL_NAME = "realName"; //$NON-NLS-1$
public static final String JID_RESOURCE = "/VASSAL"; //$NON-NLS-1$
public static final String ROOM_CONFIG = "roomConfig"; //$NON-NLS-1$
public static final String ROOM_JID = "roomJid"; //$NON-NLS-1$
public static final String ROOM_NAME = "roomName"; //$NON-NLS-1$
public static final String OWNER = "owner"; //$NON-NLS-1$
private MessageBoard msgSvr;
private XMPPConnection conn;
private String host;
private int port = 5222;
private PropertyChangeSupport propSupport = new PropertyChangeSupport(this);
private JabberPlayer me;
private String conferenceService;
private MonitorRooms monitor;
private CommandEncoder encoder;
private final JabberRoom defaultRoom;
private MultiUserChat currentChat;
private AccountInfo account;
private SynchEncoder synchEncoder;
protected SoundEncoder soundEncoder;
protected PrivateChatEncoder privateChatEncoder;
// protected MessageBoardControlsInitializer messageBoardControls;
protected RoomInteractionControlsInitializer roomControls;
// protected ServerStatusControlsInitializer serverStatusControls;
protected SimpleStatusControlsInitializer playerStatusControls;
protected JabberPlayer.Manager playerMgr = new JabberPlayer.Manager();
protected JabberRoom.Manager roomMgr = new JabberRoom.Manager();
protected PropertyChangeListener idChangeListener;
protected UserStatusListener kickListener;
protected InvitationListener inviteListener;
protected ParticipantStatusListener userListener;
public JabberClient(CommandEncoder encoder, String host, int port, AccountInfo account) {
XMPPConnection.DEBUG_ENABLED = "true".equals(System.getProperty("debugJabber"));
this.host = host;
this.conferenceService = "conference." + host; //$NON-NLS-1$
this.encoder = encoder;
this.account = account;
defaultRoom = roomMgr.getRoomByName(this, DEFAULT_ROOM_NAME);
// messageBoardControls = new MessageBoardControlsInitializer(Resources.getString("Chat.messages"), msgSvr); //$NON-NLS-1$
roomControls = new LockableJabberRoomControls(this);
roomControls.addPlayerActionFactory(ShowProfileAction.factory());
roomControls.addPlayerActionFactory(SynchAction.factory(this));
final PrivateChatManager privateChatManager = new PrivateChatManager(this);
roomControls.addPlayerActionFactory(PrivateMessageAction.factory(this, privateChatManager));
roomControls.addPlayerActionFactory(SendSoundAction.factory(this, Resources.getString("Chat.send_wakeup"), "wakeUpSound", "phone1.wav")); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
roomControls.addPlayerActionFactory(InviteAction.factory(this));
roomControls.addPlayerActionFactory(KickAction.factory(this));
// serverStatusControls = new ServerStatusControlsInitializer(serverStatus);
playerStatusControls = new SimpleStatusControlsInitializer(this);
synchEncoder = new SynchEncoder(this,this);
soundEncoder = new SoundEncoder(this);
privateChatEncoder = new PrivateChatEncoder(this, privateChatManager);
// Listen for changes to our name via VASSAL preferences
idChangeListener = new PropertyChangeListener() {
public void propertyChange(PropertyChangeEvent evt) {
if (me != null) {
final SimpleStatus s = (SimpleStatus) me.getStatus();
s.updateStatus();
me.setStatus(s);
me.setName((String) GameModule.getGameModule().getPrefs().getValue(
GameModule.REAL_NAME));
}
if (monitor != null) {
monitor.sendStatus(me);
}
}
};
// Listen for someone kicking us from the current room
kickListener = new DefaultUserStatusListener() {
public void kicked(String kicker, String reason) {
fireStatus(Resources.getString("Chat.kicked", getRoom().getName())); //$NON-NLS-1$
setRoom(defaultRoom);
}
};
// Listen for someone inviting us to another room
inviteListener = new InvitationListener() {
public void invitationReceived(XMPPConnection conn, String room,
String inviter, String reason, String password, Message mess) {
if (INVITE.equals(reason)) {
final String playerLogin = inviter.split("@")[0]; //$NON-NLS-1$
final Player player = playerMgr.getPlayer(inviter+JID_RESOURCE);
final String playerName = player.getName() + "(" + playerLogin + ")"; //$NON-NLS-1$ //$NON-NLS-2$
final String roomName = roomMgr.getRoomByJID(JabberClient.this, room).getName();
final int i = Dialogs.showConfirmDialog(GameModule
.getGameModule().getFrame(), Resources.getString("Chat.invite_heading"), //$NON-NLS-1$
Resources.getString("Chat.invite_heading"), Resources.getString( //$NON-NLS-1$
"Chat.invitation", playerName, roomName), //$NON-NLS-1$
JOptionPane.QUESTION_MESSAGE, null,
JOptionPane.YES_NO_OPTION, "Invite" + inviter, Resources //$NON-NLS-1$
.getString("Chat.ignore_invitation")); //$NON-NLS-1$
if (i == 0) {
doInvite(inviter, roomName);
}
else {
MultiUserChat.decline(conn, room, inviter, ""); //$NON-NLS-1$
}
}
}};
// Listen for other clients leaving a room I own and revoke their membership
userListener = new DefaultParticipantStatusListener() {
public void kicked(String participant, String arg1, String arg2) {
revokeMembership(participant);
}
public void left(String participant) {
revokeMembership(participant);
}
private void revokeMembership(String participant) {
final LockableRoom room = getCurrentRoom();
if (room.isLocked() && room.isOwner(me.getJid())) {
try {
final String jid = JabberPlayer.xmppAddressToJid(participant);
currentChat.revokeMembership(playerMgr.getPlayer(jid).getId());
}
catch (XMPPException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
};
}
public void addPropertyChangeListener(String propertyName, PropertyChangeListener l) {
propSupport.addPropertyChangeListener(propertyName, l);
}
public void addPropertyChangeListener(PropertyChangeListener l) {
propSupport.addPropertyChangeListener(l);
}
public boolean isConnected() {
return conn != null && conn.isConnected();
}
public void sendToOthers(Command c) {
if (currentChat != null) {
try {
currentChat.sendMessage(encodeMessage(encoder.encode(c)));
}
// FIXME: review error message
catch (XMPPException e) {
reportXMPPException(e);
}
}
}
public void setConnected(boolean connect) {
if (connect) {
if (!isConnected()) {
if (conn != null) {
conn.disconnect();
}
try {
playerMgr.clear();
roomMgr.clear();
String username = account.getUserName();
String password = account.getPassword();
me = playerMgr.getPlayerByLogin(this, account.getUserName());
final GameModule g = GameModule.getGameModule();
final SimpleStatus s = (SimpleStatus) me.getStatus();
s.updateStatus();
me.setStatus(s);
me.setName((String) g.getPrefs().getValue(GameModule.REAL_NAME));
ConnectionConfiguration config = new ConnectionConfiguration(host, port);
config.setCompressionEnabled(true);
config.setDebuggerEnabled(XMPPConnection.DEBUG_ENABLED);
config.setReconnectionAllowed(true);
conn = new XMPPConnection(config);
conn.connect();
conn.addConnectionListener(new ConnectionListener());
try {
conn.login(username, password, "VASSAL"); //$NON-NLS-1$
}
// FIXME: review error message
catch (XMPPException e) {
// Create the account if it doesn't exist
Map<String, String> attributes = new HashMap<String, String>();
attributes.put("name", me.getName()); //$NON-NLS-1$
try {
conn.getAccountManager().createAccount(username, password, attributes);
}
// FIXME: review error message
catch (XMPPException createAccountError) {
if (createAccountError.getXMPPError() != null && createAccountError.getXMPPError().getCode() == 409) {
// Account already exists. Password is incorrect
fireStatus(Resources.getString("Chat.invalid_password", username)); //$NON-NLS-1$
setConnected(false);
return;
}
else {
setConnected(false);
throw createAccountError;
}
}
// ejabberd servers require a reconnection after an account creation before
// they will allow the user to login
conn.disconnect();
conn.connect();
// Retry the login
try {
conn.login(username, password, "VASSAL"); //$NON-NLS-1$
}
catch (XMPPException retryError) {
setConnected(false);
throw retryError;
}
VCard c = new VCard();
c.setNickName(me.getName());
c.save(conn);
}
monitor = new MonitorRooms();
monitor.init();
propSupport.firePropertyChange(CONNECTED, null, Boolean.TRUE);
fireStatus(Resources.getString("Server.connected", host + ":" + port)); //$NON-NLS-1$ //$NON-NLS-2$
setRoom(defaultRoom);
GameModule.getGameModule().addIdChangeListener(idChangeListener);
MultiUserChat.addInvitationListener(conn, inviteListener);
}
// FIXME: review error message
catch (XMPPException e) {
reportXMPPException(e);
if (e.getWrappedThrowable() != null && e.getWrappedThrowable().getLocalizedMessage() != null) {
fireStatus(e.getWrappedThrowable().getMessage());
}
setConnected(false);
}
}
}
else {
if (isConnected()) {
leaveCurrentRoom();
if (monitor != null) {
monitor.disconnect();
}
conn.disconnect();
tidyConnection();
}
}
}
private void tidyConnection() {
conn = null;
monitor = null;
currentChat = null;
propSupport.firePropertyChange(CONNECTED, null, Boolean.FALSE);
playerMgr.clear();
roomMgr.clear();
fireStatus(Resources.getString("Server.disconnected", host + ":" + port)); //$NON-NLS-1$ //$NON-NLS-2$
}
private void leaveCurrentRoom() {
if (currentChat != null) {
currentChat.leave();
currentChat.removeMessageListener(this);
currentChat.removeUserStatusListener(kickListener);
currentChat.removeParticipantStatusListener(userListener);
currentChat = null;
}
}
public void initializeControls(ChatServerControls controls) {
playerStatusControls.initializeControls(controls);
// messageBoardControls.initializeControls(controls);
roomControls.initializeControls(controls);
// serverStatusControls.initializeControls(controls);
controls.setRoomControlsVisible(true);
GameModule.getGameModule().addCommandEncoder(synchEncoder);
GameModule.getGameModule().addCommandEncoder(privateChatEncoder);
GameModule.getGameModule().addCommandEncoder(soundEncoder);
controls.getRoomTree().setCellRenderer(new LockableRoomTreeRenderer());
}
public void uninitializeControls(ChatServerControls controls) {
// messageBoardControls.uninitializeControls(controls);
roomControls.uninitializeControls(controls);
playerStatusControls.uninitializeControls(controls);
// serverStatusControls.uninitializeControls(controls);
GameModule.getGameModule().removeCommandEncoder(synchEncoder);
GameModule.getGameModule().removeCommandEncoder(privateChatEncoder);
GameModule.getGameModule().removeCommandEncoder(soundEncoder);
}
public void processPacket(Packet packet) {
Message m = (Message) packet;
if (!m.getFrom().equals(currentChat.getRoom() + "/" + currentChat.getNickname())) { //$NON-NLS-1$
propSupport.firePropertyChange(INCOMING_MSG, null, decodeMessage(m.getBody()));
}
}
public void processServerMessage(String subject, String message) {
final GameModule g = GameModule.getGameModule();
g.warn("##### " + Resources.getString("JabberClient.message_from_admin", host+":"+port)); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
if (subject != null) {
g.warn(Resources.getString("JabberClient.subject")+subject); //$NON-NLS-1$
}
g.warn(message);
g.warn("##### " + Resources.getString("JabberClient.end_message")); //$NON-NLS-1$ //$NON-NLS-2$
}
public Room getRoom() {
return monitor.getCurrentRoom();
}
public JabberRoom getRoomByName(String name) {
return roomMgr.getRoomByName(this, name);
}
public String getCurrentRoomJID() {
return currentChat == null ? null : currentChat.getRoom();
}
public LockableRoom getCurrentRoom() {
return monitor.getCurrentRoom();
}
public void setRoom(String roomName) {
setRoom(roomMgr.getRoomByName(this, roomName));
}
public void setRoom(Room r) {
JabberRoom newRoom = null;
try {
if (r instanceof JabberRoom) {
newRoom = (JabberRoom) r;
}
else {
newRoom = roomMgr.getRoomByName(this, r.getName());
}
if (!newRoom.equals(getRoom())) {
final String failedToJoinMessage = newRoom.canJoin(me);
if (failedToJoinMessage != null) {
fireStatus(Resources.getString("Chat.failed_to_join", newRoom.getName(), failedToJoinMessage)); //$NON-NLS-1$
return;
}
leaveCurrentRoom();
currentChat = newRoom.join(this, (JabberPlayer) getUserInfo());
if (newRoom.isOwnedByMe()) {
currentChat.addParticipantStatusListener(userListener);
}
fireStatus(Resources.getString("Chat.joined_room", newRoom.getName())); //$NON-NLS-1$
if (!newRoom.isOwnedByMe() && ! isDefaultRoom(newRoom)) {
new SynchAction(newRoom.getOwningPlayer(), this).actionPerformed(null);
GameModule.getGameModule().warn(Resources.getString("Chat.synchronize_complete")); //$NON-NLS-1$
}
else {
SynchAction.clearSynchRoom();
}
currentChat.addUserStatusListener(kickListener);
monitor.sendRoomChanged();
monitor.sendStatus(me, newRoom);
}
}
// FIXME: review error message
catch (XMPPException e) {
reportXMPPException(e);
String mess = null;
if (e.getXMPPError() != null) {
final XMPPError error = e.getXMPPError();
if (error.getCode() == 407) {
mess = Resources.getString("Chat.not_a_member"); //$NON-NLS-1$
}
else {
mess = e.getXMPPError().getMessage();
if (mess == null) {
mess = e.getXMPPError().getCondition();
}
}
}
else {
mess = e.getMessage();
}
fireStatus(Resources.getString("Chat.failed_to_join", newRoom.getName(), mess)); //$NON-NLS-1$
}
}
public Room[] getAvailableRooms() {
return monitor.getAvailableRooms();
}
protected void fireRoomsUpdated() {
propSupport.firePropertyChange(AVAILABLE_ROOMS, null, getAvailableRooms());
propSupport.firePropertyChange(ROOM, null, getRoom());
}
protected void fireStatus(String msg) {
propSupport.firePropertyChange(STATUS, null, msg);
}
public Player getUserInfo() {
return playerMgr.getPlayerByLogin(this, account.getUserName());
}
public void setUserInfo(Player p) {
if (monitor != null) {
monitor.sendStatus((JabberPlayer) p);
}
}
public String getDefaultRoomName() {
return defaultRoom.getName();
}
public boolean isDefaultRoom(Room r) {
return r == null ? false : r.getName().equals(getDefaultRoomName());
}
public void sendTo(Player recipient, Command c) {
Chat chat = conn.getChatManager().createChat(((JabberPlayer) recipient).getJid(), null);
try {
chat.sendMessage(encodeMessage(encoder.encode(c)));
}
// FIXME: review error message
catch (XMPPException e) {
reportXMPPException(e);
}
}
/** Can a player be invited to this room? */
public boolean isInvitable(Player invitee) {
// invitee is not me
if (!invitee.equals(me)) {
// invitee is in a different room
final JabberRoom room = monitor.getCurrentRoom();
if (!room.contains(invitee)) {
// I own the current room and it is locked
if (room.isOwnedByMe() && room.isLocked()) {
return true;
}
}
}
return false;
}
/** Send invitation to player */
public void sendInvite(Player invitee) {
try {
currentChat.grantMembership(invitee.getId());
}
catch (XMPPException e) {
ErrorDialog.bug(new Throwable("Unable to grant membership to room "+getCurrentRoom().getName()+" to player "+invitee.getId(), e)); //$NON-NLS-1$ //$NON-NLS-2$
}
currentChat.invite(((JabberPlayer) invitee).getRawJid(), INVITE); //$NON-NLS-1$
}
/** Process an invitation */
public void doInvite(String playerId, String roomName) {
setRoom(roomName);
}
/** Is a player kickable from this room? */
public boolean isKickable(Player kickee) {
// kickee is not me
if (!kickee.equals(me)) {
// kickee is in this room
final JabberRoom room = monitor.getCurrentRoom();
if (room.contains(kickee)) {
// I own the current room and it is locked
if (room.isOwnedByMe() && room.isLocked()) {
return true;
}
}
}
return false;
}
/** Kick a player from this room */
public void doKick(Player kickee) {
try {
currentChat.kickParticipant(((JabberPlayer) kickee).getLoginName(), ""); //$NON-NLS-1$
}
catch (XMPPException e) {
// TODO Error - unable to kick, I must not be owner???
e.printStackTrace();
}
}
private void reportXMPPException(XMPPException e) {
e.printStackTrace();
}
public MessageBoard getMessageServer() {
return msgSvr;
}
public ServerStatus getStatusServer() {
return this;
}
public XMPPConnection getConnection() {
return conn;
}
public String getModule() {
return "vassal-" + GameModule.getGameModule().getGameName(); //$NON-NLS-1$
}
public String getConferenceService() {
return conferenceService;
}
public static String unescapeNode(String node) {
return StringUtils.unescapeNode(node);
}
/**
* Messages must be encoded to pass through Jabber:
* 1. To remove Escape characters (Vassal sub-command separator)
* 2. To hide the raw Vassal commands from observers on the chat room using a Jabber Client.
*
* @param clearText
* @return encoded text
*/
protected String encodeMessage(String clearText) {
ObfuscatingOutputStream out = null;
final FastByteArrayOutputStream ba = new FastByteArrayOutputStream();
try {
out = new ObfuscatingOutputStream(ba);
out.write(clearText.getBytes("UTF-8")); //$NON-NLS-1$
}
catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
catch (IOException e) {
e.printStackTrace();
}
finally {
IOUtils.closeQuietly(out);
}
final String encodedText = new String(ba.toByteArray());
IOUtils.closeQuietly(ba);
return encodedText;
}
/**
* Encode text encoded by encodeMessage
*
* @param encodedMessage
* @return decoded text
*/
protected String decodeMessage(String encodedMessage) {
final ByteArrayInputStream ba = new ByteArrayInputStream(encodedMessage.getBytes());
DeobfuscatingInputStream in = null;
String clearText = "";
try {
in = new DeobfuscatingInputStream(ba);
clearText = IOUtils.toString(in, "UTF-8"); //$NON-NLS-1$
}
catch (IOException e) {
e.printStackTrace();
}
finally {
IOUtils.closeQuietly(ba);
IOUtils.closeQuietly(in);
}
return clearText;
}
/**
* Toggle the lock state on the room.
*/
public void lockRoom(LockableRoom r) {
if (r instanceof JabberRoom) {
final JabberRoom room = (JabberRoom) r;
room.toggleLock(currentChat);
try {
monitor.sendRoomChanged();
}
catch (XMPPException e) {
// Ignore errors - we don't want to know at this point
}
}
}
/**
* VASSAL clients join a common room, named for the module, from which they communicate information about which
* players have joined which rooms, etc.
*
* @author rodneykinney
*
*/
private class MonitorRooms implements PacketListener, ParticipantStatusListener {
private static final String ROOM_CHANGE_ACTION = "changedRoom"; //$NON-NLS-1$
private MultiUserChat monitorRoom;
private Comparator<Room> roomSortOrder = new Comparator<Room>() {
public int compare(Room o1, Room o2) {
if (o1.equals(defaultRoom) && !o2.equals(defaultRoom)) {
return -1;
}
else if (o2.equals(defaultRoom) && !o1.equals(defaultRoom)) {
return 1;
}
else {
return o1.getName().compareTo(o2.getName());
}
}
};
public void init() throws XMPPException {
new TrackRooms().addTo(conn);
new TrackStatus(getMonitorRoomJID().toLowerCase()).addTo(conn);
new ListenForChat().addTo(conn);
monitorRoom = new MultiUserChat(conn, getMonitorRoomJID());
monitorRoom.addMessageListener(this);
monitorRoom.addParticipantStatusListener(this);
monitorRoom.join(StringUtils.parseName(conn.getUser()));
try {
// This is necessary to create the room if it doesn't already exist
monitorRoom.sendConfigurationForm(new Form(Form.TYPE_SUBMIT));
}
catch (XMPPException ex) {
// 403 code means the room already exists and user is not an owner
if (ex.getXMPPError().getCode() != 403) {
throw ex;
}
}
sendStatus(me);
}
public String getMonitorRoomJID() {
return StringUtils.escapeNode(getModule()) + "@" + getConferenceService(); //$NON-NLS-1$
}
protected void sendStatus(JabberPlayer p) {
sendStatus(p, null, p.getJoinedRoom());
}
protected void sendStatus(JabberPlayer player, JabberRoom room) {
sendStatus(player, null, room);
}
protected void sendStatus(JabberPlayer player, String recipient, JabberRoom room) {
final SimpleStatus s = (SimpleStatus) player.getStatus();
final Presence p = new Presence(Presence.Type.available);
p.setStatus(""); //$NON-NLS-1$
p.setMode(Presence.Mode.chat);
p.setProperty(SimpleStatus.LOOKING, s.isLooking());
p.setProperty(SimpleStatus.AWAY, s.isAway());
p.setProperty(SimpleStatus.IP, s.getIp());
p.setProperty(SimpleStatus.CLIENT, s.getClient());
p.setProperty(SimpleStatus.MODULE_VERSION, s.getModuleVersion());
p.setProperty(SimpleStatus.CRC, s.getCrc());
p.setProperty(REAL_NAME, player.getName()); //$NON-NLS-1$
if (room != null) {
p.setProperty(ROOM_CONFIG, room.encodeConfig());
p.setProperty(ROOM_JID, room.getJID());
p.setProperty(ROOM_NAME, room.getName());
}
p.setTo(recipient == null ? monitorRoom.getRoom() : recipient);
conn.sendPacket(p);
}
public Room[] getAvailableRooms() {
Map<JabberRoom, List<JabberPlayer>> occupants = new HashMap<JabberRoom, List<JabberPlayer>>();
for (JabberPlayer p : playerMgr.getAllPlayers()) {
JabberRoom room = p.getJoinedRoom();
if (room != null) {
List<JabberPlayer> l = occupants.get(room);
if (l == null) {
l = new ArrayList<JabberPlayer>();
occupants.put(room, l);
}
l.add(p);
}
}
if (!occupants.containsKey(defaultRoom)) {
List<JabberPlayer> l = Collections.emptyList();
occupants.put(defaultRoom, l);
}
Set<JabberRoom> rooms = occupants.keySet();
for (JabberRoom room : rooms) {
List<JabberPlayer> l = occupants.get(room);
room.setPlayers(l.toArray(new JabberPlayer[l.size()]));
}
Room[] roomArray = rooms.toArray(new Room[rooms.size()]);
Arrays.sort(roomArray, roomSortOrder);
return roomArray;
}
public JabberRoom getCurrentRoom() {
String jid = getCurrentRoomJID();
return roomMgr.getRoomByJID(JabberClient.this, jid);
}
public void sendRoomChanged() throws XMPPException {
Message m = monitorRoom.createMessage();
m.setBody(ROOM_CHANGE_ACTION);
monitorRoom.sendMessage(m);
}
public void disconnect() {
monitorRoom.leave();
}
/**
* Take the room-local JID for a player (room@conference.server/nick) and change it into an absolute address for
* that player (login@server/VASSAL)
*
* @param jid
* @return
*/
public String getAbsolutePlayerJID(String jid) {
return StringUtils.parseResource(jid) + "@" + host + JID_RESOURCE; //$NON-NLS-1$
}
private void sendRoomQuery(String jid) {
DiscoverItems disco = new DiscoverItems();
disco.setType(IQ.Type.GET);
disco.setTo(jid);
disco.setNode(QUERY_ROOMS);
conn.sendPacket(disco);
}
public void processPacket(Packet packet) {
Message m = (Message) packet;
if (ROOM_CHANGE_ACTION.equals(m.getBody())) {
String jid = getAbsolutePlayerJID(packet.getFrom());
playerMgr.getPlayer(getAbsolutePlayerJID(packet.getFrom()));
sendRoomQuery(jid);
}
}
public void joined(String participant) {
playerMgr.getPlayer(getAbsolutePlayerJID(participant));
}
public void left(String participant) {
String jid = getAbsolutePlayerJID(participant);
playerMgr.deletePlayer(jid);
fireRoomsUpdated();
}
public void kicked(String participant, String actor, String reason) {
}
public void voiceGranted(String participant) {
}
public void voiceRevoked(String participant) {
}
public void banned(String participant, String actor, String reason) {
}
public void membershipGranted(String participant) {
}
public void membershipRevoked(String participant) {
}
public void moderatorGranted(String participant) {
}
public void moderatorRevoked(String participant) {
}
public void ownershipGranted(String participant) {
}
public void ownershipRevoked(String participant) {
}
public void adminGranted(String participant) {
}
public void adminRevoked(String participant) {
}
public void nicknameChanged(String participant, String newNickname) {
}
private class TrackStatus extends PacketProcessor {
String prefix;
private PacketFilter changeStatusFilter = new PacketFilter() {
public boolean accept(Packet packet) {
boolean accept = false;
if (packet instanceof Presence) {
Presence p = (Presence) packet;
if (p.getType() == Presence.Type.available
&& p.getMode() == Presence.Mode.chat) {
String name = p.getFrom();
if (name != null && name.toLowerCase().startsWith(prefix)) {
accept = true;
}
}
}
return accept;
}};
public TrackStatus(String prefix) {
this.prefix = prefix;
}
public boolean acceptPacket(Packet packet) {
return packet instanceof Presence;
}
public void process(Packet packet) {
// Process a change of status by another user
if (changeStatusFilter.accept(packet)) {
final Presence p = (Presence) packet;
final JabberPlayer player = playerMgr.getPlayer(getAbsolutePlayerJID(p
.getFrom()));
SimpleStatus status = (SimpleStatus) player.getStatus();
final String profile = status == null ? "" : status.getProfile(); //$NON-NLS-1$
final Object looking = p.getProperty(SimpleStatus.LOOKING);
final Object away = p.getProperty(SimpleStatus.AWAY);
final String ip = p.getProperty(SimpleStatus.IP).toString();
final String client = p.getProperty(SimpleStatus.CLIENT).toString();
final String moduleVersion = p.getProperty(SimpleStatus.MODULE_VERSION)
.toString();
final String crc = p.getProperty(SimpleStatus.CRC).toString();
status = new SimpleStatus(looking == null ? false : (Boolean) looking,
away == null ? false : (Boolean) away, profile, client, ip,
moduleVersion, crc);
player.setStatus(status);
player.setName(String.valueOf(p.getProperty(REAL_NAME)));
fireRoomsUpdated();
}
// Track room ownership
if (packet instanceof Presence) {
final Presence p = (Presence) packet;
if (p.getType().equals(Presence.Type.available)) {
PacketExtension ext = p.getExtension(QUERY_USER);
JabberRoom room = null;
if (ext != null && ext instanceof MUCUser){
final String affiliation = ((MUCUser) ext).getItem().getAffiliation();
final String jid = playerMgr.getPlayer(getAbsolutePlayerJID(p.getFrom())).getJid();
String roomJid = (String) p.getProperty(ROOM_JID);
final String roomConfig = (String) p.getProperty(ROOM_CONFIG);
final String roomName = (String) p.getProperty(ROOM_NAME);
if (roomJid == null) {
roomJid = StringUtils.parseName(p.getFrom()) + "@" + getConferenceService(); //$NON-NLS-1$
}
room = roomMgr.getRoomByJID(JabberClient.this, roomJid, roomName);
if (room != null) {
if (OWNER.equals(affiliation)) {
room.addOwner(jid);
}
else {
room.removeOwner(jid);
}
}
if (roomConfig != null && room != null) {
room.decodeConfig(roomConfig);
}
}
}
}
}
}
/**
* Track available rooms
*
*/
private class TrackRooms extends PacketProcessor {
private PacketFilter roomResponseFilter = new AndFilter(new IQTypeFilter(IQ.Type.RESULT), new PacketTypeFilter(DiscoverItems.class));
private PacketFilter newPlayerFilter = new AndFilter(new PacketTypeFilter(Presence.class), new FromContainsFilter(getMonitorRoomJID()));
public TrackRooms() {
}
public void process(Packet packet) {
if (roomResponseFilter.accept(packet)) {
final DiscoverItems result = (DiscoverItems) packet;
final JabberPlayer player = playerMgr.getPlayer(packet.getFrom());
// Collect the entityID for each returned item
for (Iterator<DiscoverItems.Item> items = result.getItems(); items.hasNext();) {
final String roomJID = items.next().getEntityID();
final JabberRoom room = roomMgr.getRoomByJID(JabberClient.this, roomJID);
try {
room.setInfo(MultiUserChat.getRoomInfo(JabberClient.this
.getConnection(), roomJID));
}
catch (XMPPException e) {
// Ignore Error
}
if (!roomJID.equals(monitorRoom.getRoom())) {
player.join(roomMgr.getRoomByJID(JabberClient.this, roomJID));
}
}
fireRoomsUpdated();
}
else if (newPlayerFilter.accept(packet)) {
sendRoomQuery(getAbsolutePlayerJID(packet.getFrom()));
}
}
public boolean acceptPacket(Packet packet) {
boolean accept = false;
if (roomResponseFilter.accept(packet)) {
accept = QUERY_ROOMS.equals(((DiscoverItems) packet).getNode());
}
else if (newPlayerFilter.accept(packet)) {
accept = ((Presence) packet).isAvailable();
}
return accept;
}
}
/**
* Listen for any Private chat and pass it on to
* the client. (Also Synch's, private messages and wake ups).
*/
private class ListenForChat extends PacketProcessor {
private PacketFilter chatFilter = new MessageTypeFilter(Message.Type.chat);
private PacketFilter serverMessageFilter = new MessageTypeFilter(Message.Type.normal);
protected boolean acceptPacket(Packet packet) {
if (chatFilter.accept(packet)) {
return true;
}
if (serverMessageFilter.accept(packet)) {
return ((Message) packet).getFrom().equals(JabberClient.this.getConnection().getHost());
}
return false;
}
protected void process(Packet packet) {
if (chatFilter.accept(packet)) {
JabberClient.this.processPacket(packet);
}
else {
final Message m = ((Message) packet);
JabberClient.this.processServerMessage(m.getSubject(), m.getBody());
}
}
}
}
public CommandEncoder getEncoder() {
return encoder;
}
public void setEncoder(CommandEncoder encoder) {
this.encoder = encoder;
}
public ModuleSummary[] getHistory(String timeRange) {
return new ModuleSummary[0];
}
public ModuleSummary[] getStatus() {
ArrayList<ModuleSummary> entries = new ArrayList<ModuleSummary>();
try {
for (Iterator<HostedRoom> iter = MultiUserChat.getHostedRooms(conn, conferenceService).iterator(); iter.hasNext();) {
HostedRoom room = iter.next();
MultiUserChat.getRoomInfo(conn, room.getJid());
}
}
// FIXME: review error message
catch (XMPPException e) {
e.printStackTrace();
}
return entries.toArray(new ModuleSummary[entries.size()]);
}
public String[] getSupportedTimeRanges() {
return new String[0];
}
private class ConnectionListener implements org.jivesoftware.smack.ConnectionListener {
public void connectionClosed() {
}
public void connectionClosedOnError(Exception e) {
String msg = e.getMessage();
if (e instanceof XMPPException) {
XMPPException xe = (XMPPException) e;
if (xe.getStreamError() != null && "conflict".equals(xe.getStreamError().getCode())) { //$NON-NLS-1$
msg = Resources.getString("Server.account_in_use"); //$NON-NLS-1$
}
}
if (msg != null) {
fireStatus(msg);
}
setConnected(false);
}
public void reconnectingIn(int seconds) {
}
public void reconnectionFailed(Exception e) {
}
public void reconnectionSuccessful() {
}
}
public static void main(String[] args) {
XMPPConnection.DEBUG_ENABLED = true;
CommandEncoder c = new CommandEncoder() {
public Command decode(String command) {
System.err.println(command);
return null;
}
public String encode(Command c) {
return null;
}
};
final String username = args.length == 0 ? "test" : args[0]; //$NON-NLS-1$
final String password = args.length == 0 ? "test" : args[1]; //$NON-NLS-1$
// JabberClient client = new JabberClient(c, "63.144.41.3", 5222, username, password);
AccountInfo account = new AccountInfo() {
public String getPassword() {
return password;
}
public String getUserName() {
return username;
}
public String getModule() {
return "JabberTestModule";
}
public String getRealName() {
return username;
}
};
JabberClient client = new JabberClient(c, "localhost", 5222, account); //$NON-NLS-1$
client.addPropertyChangeListener(new PropertyChangeListener() {
public void propertyChange(PropertyChangeEvent evt) {
System.err.println(evt.getPropertyName() + "=" + evt.getNewValue()); //$NON-NLS-1$
}
});
ChatServerControls controls = new ChatServerControls();
controls.setClient(client);
JFrame f = new JFrame(username);
f.add(controls.getControls());
f.pack();
f.setVisible(true);
f.addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
}
public String getHost() {
return host;
}
public String playerToString(Player p) {
return ((JabberPlayer)p).getJid();
}
public Player stringToPlayer(String s) {
return playerMgr.getPlayer(s);
}
public static String testConnection(String host, String port, String login, String passwd) {
final StringBuffer text = new StringBuffer(Resources.getString("JabberClient.testing_connection")+host+":"+port+" "+login+"/"+passwd).append("\n"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$
if (host.length() == 0) {
return text.append(Resources.getString("JabberClient.error_no_host")).toString(); //$NON-NLS-1$
}
int portNo = 0;
try {
portNo = Integer.parseInt(port);
}
catch (NumberFormatException e) {
return text.append(Resources.getString("JabberClient.error_port_number")).toString(); //$NON-NLS-1$
}
final ConnectionConfiguration config = new ConnectionConfiguration(host, portNo);
config.setCompressionEnabled(true);
config.setDebuggerEnabled(XMPPConnection.DEBUG_ENABLED);
config.setReconnectionAllowed(true);
final XMPPConnection conn = new XMPPConnection(config);
try {
text.append(Resources.getString("JabberClient.attempting_to_connect")).append("\n"); //$NON-NLS-1$ //$NON-NLS-2$
conn.connect();
text.append(Resources.getString("JabberClient.success")).append("\n"); //$NON-NLS-1$ //$NON-NLS-2$
text.append(Resources.getString("JabberClient.attempting_to_login")).append("\n"); //$NON-NLS-1$ //$NON-NLS-2$
try {
conn.login(login, passwd, "VASSAL"); //$NON-NLS-1$
text.append(Resources.getString("JabberClient.success")); //$NON-NLS-1$
}
catch (XMPPException e) {
text.append(Resources.getString("JabberClient.login_failed")).append("\n"); //$NON-NLS-1$ //$NON-NLS-2$
text.append(Resources.getString("JabberClient.attempting_to_create")).append("\n"); //$NON-NLS-1$ //$NON-NLS-2$
final Map<String, String> attributes = new HashMap<String, String>();
attributes.put("name", GameModule.getUserId()); //$NON-NLS-1$
try {
conn.getAccountManager().createAccount(login, passwd,
attributes);
text.append(Resources.getString("JabberClient.success")); //$NON-NLS-1$
}
catch (XMPPException ex) {
text.append(Resources.getString("JabberClient.failed")).append("\n"); //$NON-NLS-1$ //$NON-NLS-2$
if (ex.getXMPPError() != null && ex.getXMPPError().getCode() == 409) {
// Account already exists. Password is incorrect
text.append(Resources.getString("Chat.invalid_password")).append("\n"); //$NON-NLS-1$ //$NON-NLS-2$
}
else {
text.append(formatXMPPError(ex));
}
}
}
}
catch (XMPPException e) {
text.append(Resources.getString("JabberClient.failed")).append("\n"); //$NON-NLS-1$ //$NON-NLS-2$
text.append(formatXMPPError(e));
}
finally {
conn.disconnect();
}
return text.toString();
}
private static String formatXMPPError(XMPPException e) {
final XMPPError error = e.getXMPPError();
if (error == null) {
return Resources.getString("Server.server_error", e.getMessage(), "", ""); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
}
else {
return Resources.getString("Server.server_error", e //$NON-NLS-1$
.getXMPPError().getMessage(), e.getXMPPError().getCondition(), e
.getXMPPError().getCode());
}
}
}