/**
* 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.Iterator;
import java.util.logging.Logger;
import org.freecolandroid.xml.stream.XMLStreamException;
import org.freecolandroid.xml.stream.XMLStreamReader;
import org.freecolandroid.xml.stream.XMLStreamWriter;
import net.sf.freecol.FreeCol;
import net.sf.freecol.common.model.Game;
import net.sf.freecol.common.model.Player;
import net.sf.freecol.common.networking.Connection;
import net.sf.freecol.common.networking.DOMMessage;
import net.sf.freecol.common.networking.MessageHandler;
import net.sf.freecol.common.networking.NoRouteToServerException;
import net.sf.freecol.common.networking.StreamedMessageHandler;
import net.sf.freecol.server.FreeColServer;
import net.sf.freecol.server.model.ServerPlayer;
import net.sf.freecol.server.networking.Server;
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, StreamedMessageHandler {
private static Logger logger = Logger.getLogger(UserConnectionHandler.class.getName());
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 connection The <code>Connection</code> the message came from.
* @param element The message to be processed.
* @return The reply.
*/
public synchronized Element handle(Connection connection, Element element) {
Element reply = null;
String type = element.getTagName();
if (type.equals("getVacantPlayers")) {
reply = getVacantPlayers(connection, element);
} else if (type.equals("disconnect")) {
reply = disconnect(connection, element);
} else {
logger.warning("Unkown request: " + type);
}
return reply;
}
/**
* Handles the main element of an XML message.
*
* @param connection The connection the message came from.
* @param in The stream containing the message.
* @param out The output stream for the reply.
*/
public void handle(Connection connection, XMLStreamReader in, XMLStreamWriter out) {
if (in.getLocalName().equals("login")) {
login(connection, in, out);
} else {
logger.warning("Unkown (streamed) request: " + in.getLocalName());
}
}
/**
* Checks if the message handler support the given message.
* @param tagName The tag name of the message to check.
* @return The result.
*/
public boolean accepts(String tagName) {
return tagName.equals("login");
}
/**
* 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.createNewRootElement("vacantPlayers");
Iterator<Player> playerIterator = game.getPlayerIterator();
while (playerIterator.hasNext()) {
ServerPlayer player = (ServerPlayer) playerIterator.next();
if (!player.isDead() && player.isEuropean() && !player.isREF()
&& (!player.isConnected() || player.isAI())) {
Element playerElement = reply.getOwnerDocument().createElement("player");
playerElement.setAttribute("username", player.getName());
reply.appendChild(playerElement);
}
}
return reply;
}
/**
* Handles a "login"-request.
*
* @param connection The connection the message is comming from.
* @param in The stream with the incoming data.
* @param out The target stream for the reply.
*/
private void login(Connection connection, XMLStreamReader in, XMLStreamWriter out) {
// TODO: Do not allow more than one (human) player to connect
// to a singleplayer game. This would be easy if we used a
// dummy connection for single-player games.
Game game = freeColServer.getGame();
Server server = freeColServer.getServer();
String username = in.getAttributeValue(null, "username");
if (username == null) {
throw new IllegalArgumentException("The attribute 'username' is missing.");
}
final String freeColVersion = in.getAttributeValue(null, "freeColVersion");
if (freeColVersion == null) {
throw new IllegalArgumentException("The attribute 'freeColVersion' is missing.");
}
if (!freeColVersion.equals(FreeCol.getVersion())) {
DOMMessage.createError(out, "server.wrongFreeColVersion", "The game versions do not match.");
return;
}
if (freeColServer.getGameState() != FreeColServer.GameState.STARTING_GAME) {
if (game.getPlayerByName(username) == null) {
DOMMessage.createError(out, "server.alreadyStarted", "The game has already been started!");
logger.warning("game state: " + freeColServer.getGameState().toString());
return;
}
ServerPlayer player = (ServerPlayer) game.getPlayerByName(username);
if (player.isConnected() && !player.isAI()) {
DOMMessage.createError(out, "server.usernameInUse", "The specified username is already in use.");
return;
}
player.setConnection(connection);
player.setConnected(true);
if (player.isAI()) {
player.setAI(false);
Element setAIElement = DOMMessage.createNewRootElement("setAI");
setAIElement.setAttribute("player", player.getId());
setAIElement.setAttribute("ai", Boolean.toString(false));
server.sendToAll(setAIElement);
}
// In case this player is the first to reconnect:
boolean isCurrentPlayer = (game.getCurrentPlayer() == null);
if (isCurrentPlayer) {
game.setCurrentPlayer(player);
}
connection.setMessageHandler(freeColServer.getInGameInputHandler());
try {
freeColServer.updateMetaServer();
} catch (NoRouteToServerException e) {}
// Make the reply:
try {
out.writeStartElement("loginConfirmed");
out.writeAttribute("admin", Boolean.toString(player.isAdmin()));
out.writeAttribute("singleplayer", Boolean.toString(freeColServer.isSingleplayer()));
out.writeAttribute("startGame", "true");
out.writeAttribute("isCurrentPlayer", Boolean.toString(isCurrentPlayer));
if (isCurrentPlayer && freeColServer.getActiveUnit() != null) {
out.writeAttribute("activeUnit",
freeColServer.getActiveUnit().getId());
}
freeColServer.getGame().toXML(out, player, false, false);
freeColServer.getMapGenerator().getMapGeneratorOptions().toXML(out);
out.writeEndElement();
} catch (XMLStreamException e) {
logger.warning("Could not write XML to stream (2).");
}
// Successful login:
server.addConnection(connection);
return;
}
// TODO: is this still needed? If game is null, the code above
// should NPE, several times.
// Wait until the game has been created:
int timeOut = 20000;
while (freeColServer.getGame() == null) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {}
timeOut -= 1000;
if (timeOut <= 0) {
DOMMessage.createError(out, "server.timeOut", "Timeout when connecting to the server.");
return;
}
}
if (!game.canAddNewPlayer()) {
DOMMessage.createError(out, "server.maximumPlayers", "Sorry, the maximum number of players reached.");
return;
}
if (game.playerNameInUse(username)) {
DOMMessage.createError(out, "server.usernameInUse", "The specified username is already in use.");
return;
}
// Create and add the new player:
boolean admin = game.getPlayers().size() == 0;
ServerPlayer newPlayer
= new ServerPlayer(game, username, admin, game.getVacantNation(),
connection.getSocket(), connection);
freeColServer.getGame().addPlayer(newPlayer);
// Send message to all players except to the new player:
Element addNewPlayer = DOMMessage.createNewRootElement("addPlayer");
addNewPlayer.appendChild(newPlayer.toXMLElement(null, addNewPlayer.getOwnerDocument()));
freeColServer.getServer().sendToAll(addNewPlayer, connection);
connection.setMessageHandler(freeColServer.getPreGameInputHandler());
try {
freeColServer.updateMetaServer();
} catch (NoRouteToServerException e) {}
// Make the reply:
try {
out.writeStartElement("loginConfirmed");
out.writeAttribute("admin", (admin ? "true" : "false"));
out.writeAttribute("singleplayer", Boolean.toString(freeColServer.isSingleplayer()));
freeColServer.getGame().toXML(out, newPlayer, false, false);
freeColServer.getMapGenerator().getMapGeneratorOptions().toXML(out);
out.writeEndElement();
} catch (XMLStreamException e) {
logger.warning("Could not write XML to stream (2).");
}
// Successful login:
server.addConnection(connection);
}
/**
* Handles a "disconnect"-message.
*
* @param connection The <code>Connection</code> the message was received on.
* @param disconnectElement The element (root element in a DOM-parsed XML tree) that
* holds all the information.
* @return The reply.
*/
private Element disconnect(Connection connection, Element disconnectElement) {
try {
connection.reallyClose();
} catch (IOException e) {
logger.warning("Could not close the connection.");
}
return null;
}
}