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(); + } + } + } } /**