/**
* 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.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
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.ChatMessage;
import net.sf.freecol.common.networking.MessageHandler;
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 the network messages.
*
* @see Controller
*/
public abstract class InputHandler extends FreeColServerHolder implements MessageHandler {
private static Logger logger = Logger.getLogger(InputHandler.class.getName());
/**
* The handler map provides named handlers for network requests. Each
* handler deals with a given request type.
*/
private final Map<String, NetworkRequestHandler> _handlerMap = Collections
.synchronizedMap(new HashMap<String, NetworkRequestHandler>());
/**
* The constructor to use.
*
* @param freeColServer The main server object.
*/
public InputHandler(final FreeColServer freeColServer) {
super(freeColServer);
// All sub-classes are forced to implement this one
register("logout", new NetworkRequestHandler() {
public Element handle(Connection connection, Element element) {
return logout(connection, element);
}
});
register("disconnect", new DisconnectHandler());
register("chat", new NetworkRequestHandler() {
public Element handle(Connection connection, Element element) {
return new ChatMessage(freeColServer.getGame(),
element).handle(freeColServer, connection);
}
});
}
/**
* Register a network request handler.
*
* @param name The name.
* @param handler The handler.
*/
protected void register(String name, NetworkRequestHandler handler) {
_handlerMap.put(name, handler);
}
/**
* Deals with incoming messages that have just been received.
*
* @param connection The <code>Connection</code> the message was received
* on.
* @param element The root element of the message.
* @return The reply.
*/
public final Element handle(Connection connection, Element element) {
String tagName = element.getTagName();
NetworkRequestHandler handler = _handlerMap.get(tagName);
if (handler != null) {
try {
return handler.handle(connection, element);
} catch (Exception e) {
// TODO: should we really catch Exception? The old code did.
logger.log(Level.WARNING, "Handler failed", e);
sendReconnectSafely(connection);
}
} else {
// Should we return an error here? The old handler returned null.
logger.warning("No handler installed for " + tagName);
}
return null;
}
/**
* Send a reconnect message ignoring (but logging) IO errors.
*
* @param connection The connection.
*/
private void sendReconnectSafely(Connection connection) {
try {
connection.sendDumping(DOMMessage.createMessage("reconnect"));
} catch (IOException e) {
logger.log(Level.WARNING, "Could not send reconnect message!", e);
}
}
/**
* Create a reply message with an error.
* TODO: should this be localized (return message name)?
*
* @param message The error message.
* @return An error message.
*/
protected Element createErrorReply(String message) {
return DOMMessage.createMessage("error",
"message", message);
}
/**
* Handles a "logout"-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.
*/
abstract protected Element logout(Connection connection, Element element);
/**
* A network request handler knows how to handle in a given request type.
*/
interface NetworkRequestHandler {
/**
* Handle a request represented by an {@link Element} and return another
* {@link Element} or null as the answer.
*
* @param connection The message's <code>Connection</code>.
* @param element The root element of the message.
* @return reply element, may be null.
*/
Element handle(Connection connection, Element element);
}
/**
* A network request handler for the current player will automatically
* return an error ("not your turn") if called by a connection
* other than that of the currently active player. If no game is active or
* if the player is unknown the same error is returned.
*/
abstract class CurrentPlayerNetworkRequestHandler implements NetworkRequestHandler {
public final Element handle(Connection conn, Element element) {
ServerPlayer player = getFreeColServer().getPlayer(conn);
if (isCurrentPlayer(player)) {
try {
return handle(player, conn, element);
} catch (Exception e) {
logger.log(Level.WARNING, "Handler failure.", e);
sendReconnectSafely(conn);
return null;
}
} else {
logger.warning("Received message out of turn from "
+ player.getNation()
+ " player:"
+ element.getTagName());
return createErrorReply("Not your turn.");
}
}
/**
* Check if a player is the current player.
*
* @param player The player.
* @return true if a game is active and the player is the current one.
*/
private boolean isCurrentPlayer(Player player) {
Game game = getFreeColServer().getGame();
if (player != null && game != null) {
return player.equals(game.getCurrentPlayer());
}
return false;
}
/**
* Handle a request for the current player.
*
* @param player The player.
* @param conn The connection.
* @param element The element with the request.
* @return answer element, may be null.
*/
protected abstract Element handle(Player player, Connection conn, Element element);
}
private class DisconnectHandler implements NetworkRequestHandler {
public Element handle(Connection connection, Element disconnectElement) {
// The player should be logged out by now, but just in case:
ServerPlayer player = getFreeColServer().getPlayer(connection);
logDisconnect(connection, player);
if (player != null && player.isConnected()) {
logout(connection, null);
}
try {
connection.reallyClose();
} catch (IOException e) {
logger.warning("Could not close the connection.");
}
Server server = getFreeColServer().getServer();
if (server != null) {
server.removeConnection(connection);
}
return null;
}
private void logDisconnect(Connection connection, ServerPlayer player) {
logger.info("Disconnection by: " + connection + ((player != null) ? " (" + player.getName() + ") " : ""));
}
}
}