Kicks and bans.

Admin chat in blue.
Chat commands (/names, /kick, /ban to start).
Close Hibernate session after loading card sets when a user connects -- Oops!
Games have their own Hibernate session for their duration, and this is used instead of a per-request session for loading card sets when changing options.
Fix changing game options without having a card set selected.
This commit is contained in:
Andy Janata 2012-08-20 22:41:06 -07:00
parent 69ec34e072
commit 7fccd69b15
19 changed files with 382 additions and 63 deletions

View File

@ -28,6 +28,9 @@ Administration tools.
--%>
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<%@ page import="com.google.inject.Injector" %>
<%@ page import="com.google.inject.Key" %>
<%@ page import="com.google.inject.TypeLiteral" %>
<%@ page import="net.socialgamer.cah.CahModule.BanList" %>
<%@ page import="net.socialgamer.cah.Constants.DisconnectReason" %>
<%@ page import="net.socialgamer.cah.Constants.LongPollEvent" %>
<%@ page import="net.socialgamer.cah.Constants.LongPollResponse" %>
@ -41,6 +44,7 @@ Administration tools.
<%@ page import="java.util.Date" %>
<%@ page import="java.util.HashMap" %>
<%@ page import="java.util.Map" %>
<%@ page import="java.util.Set" %>
<%
String remoteAddr = request.getRemoteAddr();
@ -55,6 +59,7 @@ ServletContext servletContext = pageContext.getServletContext();
Injector injector = (Injector) servletContext.getAttribute(StartupUtils.INJECTOR);
ConnectedUsers connectedUsers = injector.getInstance(ConnectedUsers.class);
Set<String> banList = injector.getInstance(Key.get(new TypeLiteral<Set<String>>(){}, BanList.class));
// process verbose toggle
String verboseParam = request.getParameter("verbose");
@ -84,13 +89,38 @@ if (kickParam != null) {
return;
}
// process ban
String banParam = request.getParameter("ban");
if (banParam != null) {
User user = connectedUsers.getUser(banParam);
if (user != null) {
Map<ReturnableData, Object> data = new HashMap<ReturnableData, Object>();
data.put(LongPollResponse.EVENT, LongPollEvent.BANNED.toString());
QueuedMessage qm = new QueuedMessage(MessageType.KICKED, data);
user.enqueueMessage(qm);
connectedUsers.removeUser(user, DisconnectReason.BANNED);
banList.add(user.getHostName());
}
response.sendRedirect("admin.jsp");
return;
}
// process unban
String unbanParam = request.getParameter("unban");
if (unbanParam != null) {
banList.remove(unbanParam);
response.sendRedirect("admin.jsp");
return;
}
%>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>CAH - Admin</title>
<title>PYX - Admin</title>
<style type="text/css" media="screen">
table, th, td {
border: 1px solid black;
@ -125,8 +155,8 @@ th, td {
</tr>
<tr>
<td>In Use</td>
<td><% out.print((Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory())
/ 1024L / 1024L); %></td>
<td><%= (Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory())
/ 1024L / 1024L %></td>
</tr>
<tr>
<td>Free</td>
@ -142,6 +172,25 @@ th, td {
</tr>
</table>
<br/>
Ban list:
<table>
<tr>
<th>Host</th>
<th>Actions</th>
</tr>
<%
for (String host : banList) {
%>
<tr>
<td><%= host %></td>
<td><a href="?unban=<%= host %>">Unban</a></td>
</tr>
<%
}
%>
</table>
<br/>
User list:
<table>
<tr>
<th>Username</th>
@ -154,9 +203,12 @@ th, td {
// TODO have a ban system. would need to store them somewhere.
%>
<tr>
<td><% out.print(u.getNickname()); %></td>
<td><% out.print(u.getHostName()); %></td>
<td><a href="?kick=<% out.print(u.getNickname()); %>">Kick</a></td>
<td><%= u.getNickname() %></td>
<td><%= u.getHostName() %></td>
<td>
<a href="?kick=<%= u.getNickname() %>">Kick</a>
<a href="?ban=<%= u.getNickname() %>">Ban</a>
</td>
</tr>
<%
}
@ -168,7 +220,7 @@ Boolean verboseDebugObj = (Boolean) servletContext.getAttribute(StartupUtils.VER
boolean verboseDebug = verboseDebugObj != null ? verboseDebugObj.booleanValue() : false;
%>
<p>
Verbose logging is currently <strong><% out.print(verboseDebug ? "ON" : "OFF"); %></strong>.
Verbose logging is currently <strong><%= verboseDebug ? "ON" : "OFF" %></strong>.
<a href="?verbose=on">Turn on.</a> <a href="?verbose=off">Turn off.</a>
</p>

View File

@ -53,6 +53,14 @@ to, for instance, display the number of connected players.
</p>
<p>Recent Changes:</p>
<ul>
<li>21 August, 6:00 AM UTC:<ul>
<li>Ban list. Only admins can ban.</li>
<li>Chat from admins shows up in blue.</li>
<li>
Currently, the admin list contains just me and a close friend. I am not taking applications.
</li>
<li>Performance and stability tweaks.</li>
</ul></li>
<li>7 July, 10:00 PM UTC:<ul>
<li>Proper Card Set support. Currently, only I can define the cards and card sets, but I hope to
eventually let users define their own. This leads into the next item...</li>

View File

@ -167,3 +167,11 @@ cah.ajax.SuccessHandlers[cah.$.AjaxOperation.JUDGE_SELECT] = function(data) {
cah.ajax.SuccessHandlers[cah.$.AjaxOperation.CHANGE_GAME_OPTIONS] = function(data) {
// pass
};
cah.ajax.SuccessHandlers[cah.$.AjaxOperation.KICK] = function(data) {
// pass
};
cah.ajax.SuccessHandlers[cah.$.AjaxOperation.BAN] = function(data) {
// pass
};

View File

@ -109,9 +109,34 @@ function chatsubmit_click() {
if (text == "") {
return;
}
var cmd = '';
if ('/' == text.substring(0, 1)) {
cmd = text.substring(1, text.indexOf(' ') >= 0 ? text.indexOf(' ') : undefined);
if (text.indexOf(' ') >= 0) {
text = text.substring(text.indexOf(' ') + 1);
} else {
text = '';
}
}
switch (cmd) {
case '':
// TODO when I get multiple channels working, this needs to know active and pass it
cah.Ajax.build(cah.$.AjaxOperation.CHAT).withMessage(text).run();
cah.log.status("<" + cah.nickname + "> " + text);
break;
case 'kick':
cah.Ajax.build(cah.$.AjaxOperation.KICK).withNickname(text.split(' ')[0]).run();
break;
case 'ban':
// this could also be an IP address
cah.Ajax.build(cah.$.AjaxOperation.BAN).withNickname(text.split(' ')[0]).run();
break;
case 'names':
cah.Ajax.build(cah.$.AjaxOperation.NAMES).run();
break;
default:
}
$("#chat").val("");
$("#chat").focus();
}

View File

@ -8,20 +8,22 @@ cah.$.AjaxOperation = function() {
cah.$.AjaxOperation.prototype.dummyForAutocomplete = undefined;
cah.$.AjaxOperation.START_GAME = "sg";
cah.$.AjaxOperation.FIRST_LOAD = "fl";
cah.$.AjaxOperation.JUDGE_SELECT = "js";
cah.$.AjaxOperation.LOG_OUT = "lo";
cah.$.AjaxOperation.BAN = "b";
cah.$.AjaxOperation.JUDGE_SELECT = "js";
cah.$.AjaxOperation.GAME_LIST = "ggl";
cah.$.AjaxOperation.CHANGE_GAME_OPTIONS = "cgo";
cah.$.AjaxOperation.GET_GAME_INFO = "ggi";
cah.$.AjaxOperation.PLAY_CARD = "pc";
cah.$.AjaxOperation.CREATE_GAME = "cg";
cah.$.AjaxOperation.KICK = "K";
cah.$.AjaxOperation.ADMIN_SET_VERBOSE_LOG = "svl";
cah.$.AjaxOperation.GET_CARDS = "gc";
cah.$.AjaxOperation.JOIN_GAME = "jg";
cah.$.AjaxOperation.REGISTER = "r";
cah.$.AjaxOperation.CHAT = "c";
cah.$.AjaxOperation.LEAVE_GAME = "lg";
cah.$.AjaxOperation.NAMES = "gn";
cah.$.AjaxOperation.LEAVE_GAME = "lg";
cah.$.AjaxRequest = function() {
// Dummy constructor to make Eclipse auto-complete.
@ -84,6 +86,7 @@ cah.$.DisconnectReason = function() {
// Dummy constructor to make Eclipse auto-complete.
};
cah.$.DisconnectReason.prototype.dummyForAutocomplete = undefined;
cah.$.DisconnectReason.BANNED = "B&";
cah.$.DisconnectReason.PING_TIMEOUT = "pt";
cah.$.DisconnectReason.KICKED = "k";
cah.$.DisconnectReason.MANUAL = "man";
@ -103,18 +106,21 @@ cah.$.ErrorCode.SESSION_EXPIRED = "se";
cah.$.ErrorCode.BAD_OP = "bo";
cah.$.ErrorCode.NO_SESSION = "ns";
cah.$.ErrorCode.NOT_REGISTERED = "nr";
cah.$.ErrorCode.NOT_JUDGE = "nj";
cah.$.ErrorCode.OP_NOT_SPECIFIED = "ons";
cah.$.ErrorCode.NOT_JUDGE = "nj";
cah.$.ErrorCode.WRONG_PASSWORD = "wp";
cah.$.ErrorCode.NOT_IN_THAT_GAME = "nitg";
cah.$.ErrorCode.NICK_IN_USE = "niu";
cah.$.ErrorCode.SERVER_ERROR = "serr";
cah.$.ErrorCode.GAME_FULL = "gf";
cah.$.ErrorCode.NO_NICK_SPECIFIED = "nns";
cah.$.ErrorCode.NOT_ADMIN = "na";
cah.$.ErrorCode.NOT_YOUR_TURN = "nyt";
cah.$.ErrorCode.BANNED = "B&";
cah.$.ErrorCode.INVALID_NICK = "in";
cah.$.ErrorCode.ALREADY_STARTED = "as";
cah.$.ErrorCode.BAD_REQUEST = "br";
cah.$.ErrorCode.NO_SUCH_USER = "nsu";
cah.$.ErrorCode.DO_NOT_HAVE_CARD = "dnhc";
cah.$.ErrorCode.MESSAGE_TOO_LONG = "mtl";
cah.$.ErrorCode.NOT_ENOUGH_PLAYERS = "nep";
@ -136,19 +142,22 @@ cah.$.ErrorCode_msg['ngs'] = "No game specified.";
cah.$.ErrorCode_msg['ic'] = "Invalid card specified.";
cah.$.ErrorCode_msg['bo'] = "Invalid operation.";
cah.$.ErrorCode_msg['dnhc'] = "You don't have that card.";
cah.$.ErrorCode_msg['cjag'] = "You cannot join another game.";
cah.$.ErrorCode_msg['ons'] = "Operation not specified.";
cah.$.ErrorCode_msg['cjag'] = "You cannot join another game.";
cah.$.ErrorCode_msg['ig'] = "Invalid game specified.";
cah.$.ErrorCode_msg['nns'] = "No nickname specified.";
cah.$.ErrorCode_msg['ngh'] = "Only the game host can do that.";
cah.$.ErrorCode_msg['nec'] = "You must select at least one base card set.";
cah.$.ErrorCode_msg['serr'] = "An error occured on the server.";
cah.$.ErrorCode_msg['nsu'] = "No such user.";
cah.$.ErrorCode_msg['wp'] = "That password is incorrect.";
cah.$.ErrorCode_msg['as'] = "The game has already started.";
cah.$.ErrorCode_msg['se'] = "Your session has expired. Refresh the page.";
cah.$.ErrorCode_msg['in'] = "Nickname must contain only upper and lower case letters, numbers, or underscores, must be 3 to 30 characters long, and must not start with a number.";
cah.$.ErrorCode_msg['nms'] = "No message specified.";
cah.$.ErrorCode_msg['na'] = "You are not an administrator.";
cah.$.ErrorCode_msg['niu'] = "Nickname is already in use.";
cah.$.ErrorCode_msg['B&'] = "Banned.";
cah.$.ErrorCode_msg['ad'] = "Access denied.";
cah.$.ErrorCode_msg['nj'] = "You aren't the judge.";
@ -219,6 +228,7 @@ cah.$.LongPollEvent = function() {
// Dummy constructor to make Eclipse auto-complete.
};
cah.$.LongPollEvent.prototype.dummyForAutocomplete = undefined;
cah.$.LongPollEvent.BANNED = "B&";
cah.$.LongPollEvent.KICKED = "k";
cah.$.LongPollEvent.HURRY_UP = "hu";
cah.$.LongPollEvent.KICKED_FROM_GAME_IDLE = "kfgi";

View File

@ -43,8 +43,11 @@ cah.longpoll.EventHandlers[cah.$.LongPollEvent.NEW_PLAYER] = function(data) {
cah.longpoll.EventHandlers[cah.$.LongPollEvent.PLAYER_LEAVE] = function(data) {
var friendly_reason = "Leaving";
switch (data[cah.$.LongPollResponse.REASON]) {
case cah.$.DisconnectReason.BANNED:
friendly_reason = "Banned";
break;
case cah.$.DisconnectReason.KICKED:
friendly_reason = "Kicked by server";
friendly_reason = "Kicked by server administrator";
break;
case cah.$.DisconnectReason.MANUAL:
friendly_reason = "Leaving";
@ -70,6 +73,15 @@ cah.longpoll.EventHandlers[cah.$.LongPollEvent.KICKED] = function() {
$("#info_area").empty();
};
cah.longpoll.EventHandlers[cah.$.LongPollEvent.BANNED] = function() {
cah.log.status("You have been banned by the server administrator.");
cah.longpoll.Resume = false;
$("input").attr("disabled", "disabled");
$("#menubar_left").empty();
$("#main").empty();
$("#info_area").empty();
};
cah.longpoll.EventHandlers[cah.$.LongPollEvent.CHAT] = function(data) {
// TODO deal with multiple channels eventually
// don't display our own chat

View File

@ -23,6 +23,12 @@
package net.socialgamer.cah;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import net.socialgamer.cah.data.GameManager;
import net.socialgamer.cah.data.GameManager.GameId;
import net.socialgamer.cah.data.GameManager.MaxGames;
@ -30,6 +36,7 @@ import net.socialgamer.cah.data.GameManager.MaxGames;
import org.hibernate.Session;
import com.google.inject.AbstractModule;
import com.google.inject.BindingAnnotation;
import com.google.inject.Provides;
@ -53,7 +60,7 @@ public class CahModule extends AbstractModule {
@Provides
@MaxGames
Integer provideMaxGames() {
return 30;
return 60;
}
/**
@ -64,4 +71,21 @@ public class CahModule extends AbstractModule {
Session provideHibernateSession() {
return HibernateUtil.instance.sessionFactory.openSession();
}
private final static Set<String> banList = Collections.synchronizedSet(new HashSet<String>());
/**
* @return A mutable Set of IP addresses (in String format) which are banned. This Set is
* thread-safe.
*/
@Provides
@BanList
Set<String> provideBanList() {
return banList;
}
@BindingAnnotation
@Retention(RetentionPolicy.RUNTIME)
public @interface BanList {
}
}

View File

@ -92,6 +92,10 @@ public class Constants {
* Reason why a client disconnected.
*/
public enum DisconnectReason {
/**
* The client was banned by the server administrator.
*/
BANNED("B&"),
/**
* The client was kicked by the server administrator.
*/
@ -150,6 +154,7 @@ public class Constants {
*/
public enum AjaxOperation {
ADMIN_SET_VERBOSE_LOG("svl"),
BAN("b"),
CHANGE_GAME_OPTIONS("cgo"),
CHAT("c"),
CREATE_GAME("cg"),
@ -162,6 +167,7 @@ public class Constants {
GET_GAME_INFO("ggi"),
JOIN_GAME("jg"),
JUDGE_SELECT("js"),
KICK("K"),
LEAVE_GAME("lg"),
LOG_OUT("lo"),
/**
@ -268,6 +274,8 @@ public class Constants {
ALREADY_STARTED("as", "The game has already started."),
BAD_OP("bo", "Invalid operation."),
BAD_REQUEST("br", "Bad request."),
@DuplicationAllowed
BANNED(DisconnectReason.BANNED, "Banned."),
CANNOT_JOIN_ANOTHER_GAME("cjag", "You cannot join another game."),
DO_NOT_HAVE_CARD("dnhc", "You don't have that card."),
GAME_FULL("gf", "That game is full. Join another."),
@ -289,6 +297,8 @@ public class Constants {
NO_MSG_SPECIFIED("nms", "No message specified."),
NO_NICK_SPECIFIED("nns", "No nickname specified."),
NO_SESSION("ns", "Session not detected. Make sure you have cookies enabled."),
NO_SUCH_USER("nsu", "No such user."),
NOT_ADMIN("na", "You are not an administrator."),
NOT_ENOUGH_CARDS("nec", "You must select at least one base card set."),
NOT_ENOUGH_PLAYERS("nep", "There are not enough players to start the game."),
NOT_GAME_HOST("ngh", "Only the game host can do that."),
@ -317,6 +327,11 @@ public class Constants {
this.message = message;
}
ErrorCode(final Enum<?> code, final String message) {
this.code = code.toString();
this.message = message;
}
@Override
public String toString() {
return code;
@ -332,6 +347,8 @@ public class Constants {
* Events that can be returned in a long poll response.
*/
public enum LongPollEvent {
@DuplicationAllowed
BANNED(DisconnectReason.BANNED),
@DuplicationAllowed
CHAT(AjaxOperation.CHAT),
GAME_BLACK_RESHUFFLE("gbr"),

View File

@ -52,6 +52,8 @@ import net.socialgamer.cah.db.BlackCard;
import net.socialgamer.cah.db.CardSet;
import net.socialgamer.cah.db.WhiteCard;
import org.hibernate.Session;
import com.google.inject.Inject;
import com.sun.istack.internal.Nullable;
@ -129,6 +131,7 @@ public class Game {
private int scoreGoal = 8;
private final Set<CardSet> cardSets = new HashSet<CardSet>();
private String password = "";
private final Session hibernateSession;
/**
* Create a new game.
@ -143,13 +146,18 @@ public class Game {
*/
@Inject
public Game(@GameId final Integer id, final ConnectedUsers connectedUsers,
final GameManager gameManager) {
final GameManager gameManager, final Session hibernateSession) {
this.id = id;
this.connectedUsers = connectedUsers;
this.gameManager = gameManager;
this.hibernateSession = hibernateSession;
state = GameState.LOBBY;
}
public Session getHibernateSession() {
return hibernateSession;
}
/**
* Add a player to the game.
*

View File

@ -158,8 +158,8 @@ public class GameManager implements Provider<Integer> {
nextId = gameId;
}
// remove the players from the game
final List<User> users = game.getUsers();
for (final User user : users) {
final List<User> usersToRemove = game.getUsers();
for (final User user : usersToRemove) {
game.removePlayer(user);
}

View File

@ -0,0 +1,75 @@
package net.socialgamer.cah.handlers;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import javax.servlet.http.HttpSession;
import net.socialgamer.cah.CahModule.BanList;
import net.socialgamer.cah.Constants.AjaxOperation;
import net.socialgamer.cah.Constants.AjaxRequest;
import net.socialgamer.cah.Constants.DisconnectReason;
import net.socialgamer.cah.Constants.ErrorCode;
import net.socialgamer.cah.Constants.LongPollEvent;
import net.socialgamer.cah.Constants.LongPollResponse;
import net.socialgamer.cah.Constants.ReturnableData;
import net.socialgamer.cah.Constants.SessionAttribute;
import net.socialgamer.cah.RequestWrapper;
import net.socialgamer.cah.data.ConnectedUsers;
import net.socialgamer.cah.data.QueuedMessage;
import net.socialgamer.cah.data.QueuedMessage.MessageType;
import net.socialgamer.cah.data.User;
import com.google.inject.Inject;
public class BanHandler extends Handler {
public static final String OP = AjaxOperation.BAN.toString();
private final ConnectedUsers connectedUsers;
private final Set<String> banList;
@Inject
public BanHandler(final ConnectedUsers connectedUsers, @BanList final Set<String> banList) {
this.connectedUsers = connectedUsers;
this.banList = banList;
}
@Override
public Map<ReturnableData, Object> handle(final RequestWrapper request, final HttpSession session) {
final User user = (User) session.getAttribute(SessionAttribute.USER);
assert (user != null);
if (!user.isAdmin()) {
return error(ErrorCode.NOT_ADMIN);
}
if (null == request.getParameter(AjaxRequest.NICKNAME)
|| request.getParameter(AjaxRequest.NICKNAME).isEmpty()) {
return error(ErrorCode.NO_NICK_SPECIFIED);
}
final String banIp;
final User kickUser = connectedUsers.getUser(request.getParameter(AjaxRequest.NICKNAME));
if (null != kickUser) {
banIp = kickUser.getHostName();
final Map<ReturnableData, Object> kickData = new HashMap<ReturnableData, Object>();
kickData.put(LongPollResponse.EVENT, LongPollEvent.BANNED.toString());
final QueuedMessage qm = new QueuedMessage(MessageType.KICKED, kickData);
kickUser.enqueueMessage(qm);
connectedUsers.removeUser(kickUser, DisconnectReason.BANNED);
logger.info(String.format("Banning %s (%s) by request of %s", kickUser.getNickname(), banIp,
user.getNickname()));
} else {
banIp = request.getParameter(AjaxRequest.NICKNAME);
logger.info(String.format("Banning %s by request of %s", banIp, user.getNickname()));
}
banList.add(banIp);
return new HashMap<ReturnableData, Object>();
}
}

View File

@ -18,8 +18,6 @@ import net.socialgamer.cah.data.GameManager;
import net.socialgamer.cah.data.User;
import net.socialgamer.cah.db.CardSet;
import org.hibernate.Session;
import com.google.inject.Inject;
@ -27,12 +25,9 @@ public class ChangeGameOptionHandler extends GameWithPlayerHandler {
public static final String OP = AjaxOperation.CHANGE_GAME_OPTIONS.toString();
private final Session hibernateSession;
@Inject
public ChangeGameOptionHandler(final GameManager gameManager, final Session hibernateSession) {
public ChangeGameOptionHandler(final GameManager gameManager) {
super(gameManager);
this.hibernateSession = hibernateSession;
}
@Override
@ -51,7 +46,10 @@ public class ChangeGameOptionHandler extends GameWithPlayerHandler {
final String[] cardSetsParsed = request.getParameter(AjaxRequest.CARD_SETS).split(",");
final Set<CardSet> cardSets = new HashSet<CardSet>();
for (final String cardSetId : cardSetsParsed) {
cardSets.add((CardSet) hibernateSession.load(CardSet.class, Integer.parseInt(cardSetId)));
if (!cardSetId.isEmpty()) {
cardSets.add((CardSet) game.getHibernateSession().load(CardSet.class,
Integer.parseInt(cardSetId)));
}
}
String password = request.getParameter(AjaxRequest.PASSWORD);
if (password == null) {

View File

@ -99,6 +99,7 @@ public class FirstLoadHandler extends Handler {
}
ret.put(AjaxResponse.CARD_SETS, cardSetsData);
transaction.commit();
hibernateSession.close();
return ret;
}

View File

@ -23,10 +23,8 @@
package net.socialgamer.cah.handlers;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.servlet.http.HttpSession;
@ -36,8 +34,6 @@ import net.socialgamer.cah.Constants.ErrorCode;
import net.socialgamer.cah.Constants.ReturnableData;
import net.socialgamer.cah.RequestWrapper;
import org.hibernate.Session;
/**
* Implementations of this interface MUST also have a public static final String OP. There will be
@ -46,7 +42,7 @@ import org.hibernate.Session;
* @author Andy Janata (ajanata@socialgamer.net)
*/
public abstract class Handler {
private final Logger logger = Logger.getLogger("net.socialgamer.cah.handlers.Handler");
protected final Logger logger = Logger.getLogger("net.socialgamer.cah.handlers.Handler");
/**
* Handle a request.
@ -80,28 +76,29 @@ public abstract class Handler {
* did not already close it.
*/
public final void cleanUp() {
for (final Field field : this.getClass().getDeclaredFields()) {
if (field.getType() == Session.class) {
try {
// This Handler had a Hibernate Session. Try to close it if it wasn't already closed.
// This is extremely dirty but also extremely awesome to not have problems if it is
// forgotten.
field.setAccessible(true);
final Session session = (Session) field.get(this);
if (session.isOpen()) {
session.close();
logger.log(Level.INFO, "Closing unclosed Hibernate Session in "
+ this.getClass().getName());
}
} catch (final Exception e) {
// Something prevented us from ignoring access control check, so we can't close the
// session. Log about it and continue.
e.printStackTrace();
logger.log(Level.SEVERE, "Unable to reflect and get Hibernate Session from "
+ this.getClass().getName());
logger.log(Level.SEVERE, e.toString());
}
}
}
// this actually breaks stuff, I'll have to think it through later.
// for (final Field field : this.getClass().getDeclaredFields()) {
// if (field.getType() == Session.class) {
// try {
// // This Handler had a Hibernate Session. Try to close it if it wasn't already closed.
// // This is extremely dirty but also extremely awesome to not have problems if it is
// // forgotten.
// field.setAccessible(true);
// final Session session = (Session) field.get(this);
// if (session.isOpen()) {
// session.close();
// logger.log(Level.INFO, "Closing unclosed Hibernate Session in "
// + this.getClass().getName());
// }
// } catch (final Exception e) {
// // Something prevented us from ignoring access control check, so we can't close the
// // session. Log about it and continue.
// e.printStackTrace();
// logger.log(Level.SEVERE, "Unable to reflect and get Hibernate Session from "
// + this.getClass().getName());
// logger.log(Level.SEVERE, e.toString());
// }
// }
// }
}
}

View File

@ -11,6 +11,7 @@ public class Handlers {
static {
LIST = new HashMap<String, Class<? extends Handler>>();
LIST.put(AdminSetVerboseLog.OP, AdminSetVerboseLog.class);
LIST.put(BanHandler.OP, BanHandler.class);
LIST.put(ChangeGameOptionHandler.OP, ChangeGameOptionHandler.class);
LIST.put(ChatHandler.OP, ChatHandler.class);
LIST.put(CreateGameHandler.OP, CreateGameHandler.class);
@ -20,6 +21,7 @@ public class Handlers {
LIST.put(GetGameInfoHandler.OP, GetGameInfoHandler.class);
LIST.put(JoinGameHandler.OP, JoinGameHandler.class);
LIST.put(JudgeSelectHandler.OP, JudgeSelectHandler.class);
LIST.put(KickHandler.OP, KickHandler.class);
LIST.put(LeaveGameHandler.OP, LeaveGameHandler.class);
LIST.put(LogoutHandler.OP, LogoutHandler.class);
LIST.put(NamesHandler.OP, NamesHandler.class);

View File

@ -0,0 +1,66 @@
package net.socialgamer.cah.handlers;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.http.HttpSession;
import net.socialgamer.cah.Constants.AjaxOperation;
import net.socialgamer.cah.Constants.AjaxRequest;
import net.socialgamer.cah.Constants.DisconnectReason;
import net.socialgamer.cah.Constants.ErrorCode;
import net.socialgamer.cah.Constants.LongPollEvent;
import net.socialgamer.cah.Constants.LongPollResponse;
import net.socialgamer.cah.Constants.ReturnableData;
import net.socialgamer.cah.Constants.SessionAttribute;
import net.socialgamer.cah.RequestWrapper;
import net.socialgamer.cah.data.ConnectedUsers;
import net.socialgamer.cah.data.QueuedMessage;
import net.socialgamer.cah.data.QueuedMessage.MessageType;
import net.socialgamer.cah.data.User;
import com.google.inject.Inject;
public class KickHandler extends Handler {
public static final String OP = AjaxOperation.KICK.toString();
private final ConnectedUsers connectedUsers;
@Inject
public KickHandler(final ConnectedUsers connectedUsers) {
this.connectedUsers = connectedUsers;
}
@Override
public Map<ReturnableData, Object> handle(final RequestWrapper request, final HttpSession session) {
final User user = (User) session.getAttribute(SessionAttribute.USER);
assert (user != null);
if (!user.isAdmin()) {
return error(ErrorCode.NOT_ADMIN);
}
if (null == request.getParameter(AjaxRequest.NICKNAME)
|| request.getParameter(AjaxRequest.NICKNAME).isEmpty()) {
return error(ErrorCode.NO_NICK_SPECIFIED);
}
final User kickUser = connectedUsers.getUser(request.getParameter(AjaxRequest.NICKNAME));
if (null == kickUser) {
return error(ErrorCode.NO_SUCH_USER);
}
final Map<ReturnableData, Object> kickData = new HashMap<ReturnableData, Object>();
kickData.put(LongPollResponse.EVENT, LongPollEvent.KICKED.toString());
final QueuedMessage qm = new QueuedMessage(MessageType.KICKED, kickData);
kickUser.enqueueMessage(qm);
connectedUsers.removeUser(kickUser, DisconnectReason.KICKED);
logger.info(String.format("Kicking %s by request of %s", kickUser.getNickname(),
user.getNickname()));
return new HashMap<ReturnableData, Object>();
}
}

View File

@ -25,10 +25,12 @@ package net.socialgamer.cah.handlers;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;
import javax.servlet.http.HttpSession;
import net.socialgamer.cah.CahModule.BanList;
import net.socialgamer.cah.Constants;
import net.socialgamer.cah.Constants.AjaxOperation;
import net.socialgamer.cah.Constants.AjaxRequest;
@ -55,6 +57,7 @@ public class RegisterHandler extends Handler {
private static final Pattern validName = Pattern.compile("[a-zA-Z_][a-zA-Z0-9_]{2,29}");
private final ConnectedUsers users;
private final Set<String> banList;
/**
* I don't want to have to inject the entire server here, but I couldn't figure out how to
@ -63,8 +66,9 @@ public class RegisterHandler extends Handler {
* @param server
*/
@Inject
public RegisterHandler(final ConnectedUsers users) {
public RegisterHandler(final ConnectedUsers users, @BanList final Set<String> banList) {
this.users = users;
this.banList = banList;
}
@Override
@ -72,6 +76,10 @@ public class RegisterHandler extends Handler {
final HttpSession session) {
final Map<ReturnableData, Object> data = new HashMap<ReturnableData, Object>();
if (banList.contains(request.getRemoteAddr())) {
return error(ErrorCode.BANNED);
}
if (request.getParameter(AjaxRequest.NICKNAME) == null) {
return error(ErrorCode.NO_NICK_SPECIFIED);
} else {
@ -81,8 +89,8 @@ public class RegisterHandler extends Handler {
} else if (users.hasUser(nick)) {
return error(ErrorCode.NICK_IN_USE);
} else {
final User user = new User(nick, request.getRemoteHost(),
Constants.ADMIN_IP_ADDRESSES.contains(request.getRemoteHost()));
final User user = new User(nick, request.getRemoteAddr(),
Constants.ADMIN_IP_ADDRESSES.contains(request.getRemoteAddr()));
users.newUser(user);
// There is a findbugs warning on this line:
// cah/src/net/socialgamer/cah/handlers/RegisterHandler.java:85 Store of non serializable

View File

@ -36,10 +36,12 @@ import static org.junit.Assert.assertNull;
import java.util.Collection;
import java.util.HashMap;
import net.socialgamer.cah.HibernateUtil;
import net.socialgamer.cah.data.GameManager.GameId;
import net.socialgamer.cah.data.GameManager.MaxGames;
import net.socialgamer.cah.data.QueuedMessage.MessageType;
import org.hibernate.Session;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
@ -87,6 +89,12 @@ public class GameManagerTest {
Integer provideGameId() {
return gameId;
}
@SuppressWarnings("unused")
@Provides
Session provideSession() {
return HibernateUtil.instance.sessionFactory.openSession();
}
});
gameManager = injector.getInstance(GameManager.class);
@ -108,11 +116,11 @@ public class GameManagerTest {
// fill it up with 3 games
assertEquals(0, gameManager.get().intValue());
gameManager.getGames().put(0, new Game(0, cuMock, gameManager));
gameManager.getGames().put(0, new Game(0, cuMock, gameManager, null));
assertEquals(1, gameManager.get().intValue());
gameManager.getGames().put(1, new Game(1, cuMock, gameManager));
gameManager.getGames().put(1, new Game(1, cuMock, gameManager, null));
assertEquals(2, gameManager.get().intValue());
gameManager.getGames().put(2, new Game(2, cuMock, gameManager));
gameManager.getGames().put(2, new Game(2, cuMock, gameManager, null));
// make sure it says it can't make any more
assertEquals(-1, gameManager.get().intValue());
@ -120,13 +128,13 @@ public class GameManagerTest {
gameManager.destroyGame(1);
// make sure it re-uses that id
assertEquals(1, gameManager.get().intValue());
gameManager.getGames().put(1, new Game(1, cuMock, gameManager));
gameManager.getGames().put(1, new Game(1, cuMock, gameManager, null));
assertEquals(-1, gameManager.get().intValue());
// remove game 1 out from under it, to make sure it'll fix itself
gameManager.getGames().remove(1);
assertEquals(1, gameManager.get().intValue());
gameManager.getGames().put(1, new Game(1, cuMock, gameManager));
gameManager.getGames().put(1, new Game(1, cuMock, gameManager, null));
assertEquals(-1, gameManager.get().intValue());
gameManager.destroyGame(2);

View File

@ -59,7 +59,7 @@ public class GameTest {
public void setUp() throws Exception {
cuMock = createMock(ConnectedUsers.class);
gmMock = createMock(GameManager.class);
game = new Game(0, cuMock, gmMock);
game = new Game(0, cuMock, gmMock, null);
}
@SuppressWarnings("unchecked")