/**
* Copyright (C) 2002-2012 The FreeCol Team
*
* This file is part of FreeCol.
*
* FreeCol is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* FreeCol 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with FreeCol. If not, see <http://www.gnu.org/licenses/>.
*/
package net.sf.freecol.server.control;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
import net.sf.freecol.FreeCol;
import net.sf.freecol.common.model.Game;
import net.sf.freecol.common.model.Player;
import net.sf.freecol.common.model.Unit;
import net.sf.freecol.common.networking.Connection;
import net.sf.freecol.common.networking.DOMMessage;
import net.sf.freecol.common.networking.LoginMessage;
import net.sf.freecol.common.networking.MessageHandler;
import net.sf.freecol.common.networking.NoRouteToServerException;
import net.sf.freecol.server.FreeColServer;
import net.sf.freecol.server.model.ServerPlayer;
import net.sf.freecol.server.networking.Server;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
/**
* Handles a new client connection. {@link PreGameInputHandler} is set
* as the message handler when the client has successfully logged on.
*/
public final class UserConnectionHandler implements MessageHandler {
private static Logger logger = Logger.getLogger(UserConnectionHandler.class.getName());
/** The main server object. */
private final FreeColServer freeColServer;
/**
* The constructor to use.
*
* @param freeColServer The main control object.
*/
public UserConnectionHandler(FreeColServer freeColServer) {
this.freeColServer = freeColServer;
}
/**
* Handles a network message.
*
* @param conn The <code>Connection</code> the message came from.
* @param element The message to be processed.
* @return The reply.
*/
public synchronized Element handle(Connection conn, Element element) {
String type = (element == null) ? "(null)" : element.getTagName();
return ("disconnect".equals(type))
? disconnect(conn, element)
: ("getVacantPlayers".equals(type))
? getVacantPlayers(conn, element)
: ("login".equals(type))
? login(conn, element)
: unknown(type);
}
private Element unknown(String type) {
logger.warning("Unknown user connection request: " + type);
return null;
}
/**
* Handles a "disconnect"-message.
*
* @param connection The <code>Connection</code> the message was
* received on.
* @param element The <code>Element</code> (root element in a
* DOM-parsed XML tree) that holds all the information.
* @return The reply.
*/
private Element disconnect(Connection connection, Element element) {
try {
connection.reallyClose();
} catch (IOException e) {
logger.log(Level.WARNING, "Could not close the connection.", e);
}
return null;
}
/**
* Handles a "login"-request.
*
* TODO: Do not allow more than one (human) player to connect
* to a single player game. This would be easy if we used a
* dummy connection for single player games.
*
* @param connection The <code>Connection</code> the message was
* received on.
* @param element The <code>Element</code> (root element in a
* DOM-parsed XML tree) that holds all the information.
* @return The reply.
*/
private Element login(Connection connection, Element element) {
final String userName = element.getAttribute("userName");
final String version = element.getAttribute("version");
if (userName == null || "".equals(userName)) {
return DOMMessage.createError("server.missingUserName", null);
} else if (version == null || "".equals(version)) {
return DOMMessage.createError("server.missingVersion", null);
} else if (!version.equals(FreeCol.getVersion())) {
return DOMMessage.createError("server.wrongFreeColVersion",
version + " != " + FreeCol.getVersion());
}
Game game;
ServerPlayer player;
Server server = freeColServer.getServer();
Unit active = null;
boolean isCurrentPlayer = false;
MessageHandler mh;
boolean starting = freeColServer.getGameState()
== FreeColServer.GameState.STARTING_GAME;
if (starting) {
// Wait until the game has been created.
// TODO: is this still needed?
int timeOut = 20000;
while (freeColServer.getGame() == null) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {}
if ((timeOut -= 1000) <= 0) {
return DOMMessage.createError("server.timeOut", null);
}
}
game = freeColServer.getGame();
if (!game.canAddNewPlayer()) {
return DOMMessage.createError("server.maximumPlayers", null);
} else if (game.playerNameInUse(userName)) {
return DOMMessage.createError("server.userNameInUse",
userName + " is already in use.");
}
// Create and add the new player:
boolean admin = game.getPlayers().size() == 0;
player = new ServerPlayer(game, userName, admin,
game.getVacantNation(),
connection.getSocket(), connection);
game.addPlayer(player);
// Send message to all players except to the new player:
Element add = DOMMessage.createMessage("addPlayer");
add.appendChild(player.toXMLElement(null, add.getOwnerDocument()));
server.sendToAll(add, connection);
// Ready now to handle pre-game messages.
mh = freeColServer.getPreGameInputHandler();
} else { // Restoring from existing game.
game = freeColServer.getGame();
player = (ServerPlayer)game.getPlayerByName(userName);
if (player == null) {
return DOMMessage.createError("server.alreadyStarted", null);
} else if (player.isConnected() && !player.isAI()) {
return DOMMessage.createError("server.userNameInUse",
userName + " is already in use.");
}
player.setConnection(connection);
player.setConnected(true);
if (player.isAI()) {
player.setAI(false);
server.sendToAll(DOMMessage.createMessage("setAI",
"player", player.getId(),
"ai", Boolean.toString(false)));
}
// If this player is the first to reconnect, it is the
// current player.
isCurrentPlayer = game.getCurrentPlayer() == null;
if (isCurrentPlayer) {
game.setCurrentPlayer(player);
active = freeColServer.getActiveUnit();
}
// Go straight into the game.
mh = freeColServer.getInGameInputHandler();
}
connection.setMessageHandler(mh);
server.addConnection(connection);
try {
freeColServer.updateMetaServer();
} catch (NoRouteToServerException e) {
logger.log(Level.WARNING, "Unable to update meta-server.", e);
}
return new LoginMessage(player, userName, version, !starting,
freeColServer.isSinglePlayer(),
isCurrentPlayer, active,
game).toXMLElement();
}
/**
* Handles a "getVacantPlayers"-request.
*
* @param connection The connection the message came from.
* @param element The element containing the request.
* @return The reply: An XML element containing a list of the
* vacant players.
*/
private Element getVacantPlayers(Connection connection, Element element) {
Game game = freeColServer.getGame();
if (freeColServer.getGameState()
== FreeColServer.GameState.STARTING_GAME) {
return null;
}
Element reply = DOMMessage.createMessage("vacantPlayers");
Document doc = reply.getOwnerDocument();
for (Player player : game.getPlayers()) {
if (!player.isDead()
&& player.isEuropean()
&& !player.isREF()
&& (!((ServerPlayer)player).isConnected() || player.isAI())) {
Element playerElement = doc.createElement("player");
playerElement.setAttribute("username", player.getName());
reply.appendChild(playerElement);
}
}
return reply;
}
}