diff --git a/WebContent/WEB-INF/web.xml b/WebContent/WEB-INF/web.xml
new file mode 100644
index 0000000..9df711e
--- /dev/null
+++ b/WebContent/WEB-INF/web.xml
@@ -0,0 +1,44 @@
+
+
+ cah
+
+ index.html
+ index.htm
+ index.jsp
+
+
+ TestServlet
+ net.socialgamer.cah.TestServlet
+
+
+ LongPollServlet
+ net.socialgamer.cah.LongPollServlet
+
+
+ AjaxServlet
+ net.socialgamer.cah.AjaxServlet
+
+
+ Schema
+ net.socialgamer.cah.Schema
+
+
+ TestServlet
+ /TestServlet
+
+
+ LongPollServlet
+ /LongPollServlet
+
+
+ AjaxServlet
+ /AjaxServlet
+
+
+ Schema
+ /Schema
+
+
+ net.socialgamer.cah.StartupUtils
+
+
\ No newline at end of file
diff --git a/src/net/socialgamer/cah/AjaxServlet.java b/src/net/socialgamer/cah/AjaxServlet.java
index f432dfc..2a0e420 100644
--- a/src/net/socialgamer/cah/AjaxServlet.java
+++ b/src/net/socialgamer/cah/AjaxServlet.java
@@ -4,11 +4,8 @@ import java.io.IOException;
import java.io.PrintWriter;
import java.util.Map;
-import javax.servlet.Servlet;
-import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
-import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
@@ -16,9 +13,6 @@ import javax.servlet.http.HttpSession;
import net.socialgamer.cah.handlers.Handler;
import net.socialgamer.cah.handlers.Handlers;
-import com.google.inject.Guice;
-import com.google.inject.Injector;
-
/**
* Servlet implementation class AjaxServlet
@@ -29,25 +23,6 @@ import com.google.inject.Injector;
public class AjaxServlet extends CahServlet {
private static final long serialVersionUID = 1L;
- private final Injector injector;
-
- /**
- * @see HttpServlet#HttpServlet()
- */
- public AjaxServlet() {
- super();
-
- injector = Guice.createInjector();
- }
-
- /**
- * @see Servlet#init(ServletConfig)
- */
- @Override
- public void init(final ServletConfig config) throws ServletException {
- // TODO Auto-generated method stub
- }
-
/**
* @see CahServlet#doPost(HttpServletRequest request, HttpServletResponse response, HttpSession
* hSession)
@@ -76,7 +51,7 @@ public class AjaxServlet extends CahServlet {
final Handler handler;
try {
- handler = injector.getInstance(Handlers.LIST.get(op));
+ handler = getInjector().getInstance(Handlers.LIST.get(op));
} catch (final Exception e) {
returnError(out, "bad_op", "Invalid operation.", serial);
return;
diff --git a/src/net/socialgamer/cah/CahModule.java b/src/net/socialgamer/cah/CahModule.java
index d5dbca6..fc89ab7 100644
--- a/src/net/socialgamer/cah/CahModule.java
+++ b/src/net/socialgamer/cah/CahModule.java
@@ -8,7 +8,6 @@ public class CahModule extends AbstractModule {
@Override
protected void configure() {
- // TODO Auto-generated method stub
bind(Server.class).in(Singleton.class);
}
}
diff --git a/src/net/socialgamer/cah/CahServlet.java b/src/net/socialgamer/cah/CahServlet.java
index faa568e..97cef15 100644
--- a/src/net/socialgamer/cah/CahServlet.java
+++ b/src/net/socialgamer/cah/CahServlet.java
@@ -13,6 +13,8 @@ import javax.servlet.http.HttpSession;
import org.json.simple.JSONObject;
import org.json.simple.JSONValue;
+import com.google.inject.Injector;
+
/**
* Servlet implementation class CahServlet
@@ -109,4 +111,7 @@ public abstract class CahServlet extends HttpServlet {
writer.println(JSONValue.toJSONString(data));
}
+ protected Injector getInjector() {
+ return (Injector) getServletContext().getAttribute(StartupUtils.INJECTOR);
+ }
}
diff --git a/src/net/socialgamer/cah/StartupUtils.java b/src/net/socialgamer/cah/StartupUtils.java
new file mode 100644
index 0000000..31882f3
--- /dev/null
+++ b/src/net/socialgamer/cah/StartupUtils.java
@@ -0,0 +1,48 @@
+package net.socialgamer.cah;
+
+import java.util.Timer;
+
+import javax.servlet.ServletContext;
+import javax.servlet.ServletContextEvent;
+
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+import com.google.inject.servlet.GuiceServletContextListener;
+
+
+public class StartupUtils extends GuiceServletContextListener {
+
+ public static final String INJECTOR = "injector";
+
+ private static final long PING_START_DELAY = 60 * 1000;
+
+ private static final long PING_CHECK_DELAY = 5 * 1000;
+
+ private static final String PING_TIMER_NAME = "ping_timer";
+
+ @Override
+ public void contextDestroyed(final ServletContextEvent contextEvent) {
+ final ServletContext context = contextEvent.getServletContext();
+ final Timer timer = (Timer) context.getAttribute(PING_TIMER_NAME);
+ assert (timer != null);
+ timer.cancel();
+ context.removeAttribute(PING_TIMER_NAME);
+ context.removeAttribute(INJECTOR);
+ }
+
+ @Override
+ public void contextInitialized(final ServletContextEvent contextEvent) {
+ final ServletContext context = contextEvent.getServletContext();
+ final Injector injector = getInjector();
+ final UserPing ping = injector.getInstance(UserPing.class);
+ final Timer timer = new Timer();
+ timer.schedule(ping, PING_START_DELAY, PING_CHECK_DELAY);
+ context.setAttribute(PING_TIMER_NAME, timer);
+ context.setAttribute(INJECTOR, injector);
+ }
+
+ @Override
+ protected Injector getInjector() {
+ return Guice.createInjector();
+ }
+}
diff --git a/src/net/socialgamer/cah/UserPing.java b/src/net/socialgamer/cah/UserPing.java
new file mode 100644
index 0000000..87cdcae
--- /dev/null
+++ b/src/net/socialgamer/cah/UserPing.java
@@ -0,0 +1,23 @@
+package net.socialgamer.cah;
+
+import java.util.TimerTask;
+
+import net.socialgamer.cah.data.ConnectedUsers;
+
+import com.google.inject.Inject;
+
+
+public class UserPing extends TimerTask {
+
+ private final ConnectedUsers users;
+
+ @Inject
+ public UserPing(final Server server) {
+ users = server.getConnectedUsers();
+ }
+
+ @Override
+ public void run() {
+ users.checkForPingTimeouts();
+ }
+}
diff --git a/src/net/socialgamer/cah/data/ConnectedUsers.java b/src/net/socialgamer/cah/data/ConnectedUsers.java
index 9bc3f10..a966154 100644
--- a/src/net/socialgamer/cah/data/ConnectedUsers.java
+++ b/src/net/socialgamer/cah/data/ConnectedUsers.java
@@ -2,9 +2,11 @@ package net.socialgamer.cah.data;
import java.util.Collection;
import java.util.HashMap;
+import java.util.Iterator;
import java.util.Map;
import net.socialgamer.cah.data.QueuedMessage.Type;
+import net.socialgamer.cah.data.User.DisconnectReason;
/**
@@ -16,6 +18,11 @@ import net.socialgamer.cah.data.QueuedMessage.Type;
*/
public class ConnectedUsers {
+ /**
+ * Duration of a ping timeout, in nanoseconds.
+ */
+ public static final long PING_TIMEOUT = 3L * 60L * 1000L * 1000000L;
+
private final Map users = new HashMap();
public boolean hasUser(final String userName) {
@@ -33,10 +40,31 @@ public class ConnectedUsers {
}
public void removeUser(final User user, final User.DisconnectReason reason) {
- // TODO fire an event for a disconnected user to interested parties
- // synchronized (users) {
- //
- // }
+ synchronized (users) {
+ users.remove(user.getNickname());
+ notifyRemoveUser(user, reason);
+ }
+ }
+
+ private void notifyRemoveUser(final User user, final User.DisconnectReason reason) {
+ final HashMap data = new HashMap();
+ data.put("event", "player_leave");
+ data.put("nickname", user.getNickname());
+ data.put("reason", reason.toString());
+ broadcastToAll(Type.PLAYER_DISCONNECT, data);
+ }
+
+ public void checkForPingTimeouts() {
+ synchronized (users) {
+ final Iterator iterator = users.values().iterator();
+ while (iterator.hasNext()) {
+ final User u = iterator.next();
+ if (System.nanoTime() - u.getLastHeardFrom() > PING_TIMEOUT) {
+ notifyRemoveUser(u, DisconnectReason.PING_TIMEOUT);
+ iterator.remove();
+ }
+ }
+ }
}
/**