/** * Copyright (c) 2012, Andy Janata * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, are permitted * provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, this list of conditions * and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, this list of * conditions and the following disclaimer in the documentation and/or other materials provided * with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package net.socialgamer.cah.servlets; import java.io.IOException; import java.io.PrintWriter; import java.util.Enumeration; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.annotation.Nullable; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import net.socialgamer.cah.Constants.AjaxOperation; import net.socialgamer.cah.Constants.AjaxRequest; import net.socialgamer.cah.Constants.AjaxResponse; import net.socialgamer.cah.Constants.ErrorCode; import net.socialgamer.cah.Constants.ReturnableData; import net.socialgamer.cah.Constants.SessionAttribute; import net.socialgamer.cah.StartupUtils; import net.socialgamer.cah.data.User; import org.json.simple.JSONObject; import org.json.simple.JSONValue; import com.google.inject.Injector; /** * Servlet implementation class CahServlet. * * Superclass for all CAH servlets. Provides utility methods to return errors and data, and to log. * * @author Andy Janata (ajanata@socialgamer.net) */ public abstract class CahServlet extends HttpServlet { private static final long serialVersionUID = 1L; /** * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response) */ @Override protected void doPost(final HttpServletRequest request, final HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding("UTF-8"); response.setContentType("application/json"); response.setCharacterEncoding("UTF-8"); final String serialString = request.getParameter(AjaxRequest.SERIAL.toString()); int serial = -1; if (null != serialString && !"".equals(serialString)) { try { serial = Integer.parseInt(serialString); } catch (final NumberFormatException e) { // pass } } try { final HttpSession hSession = request.getSession(true); final User user = (User) hSession.getAttribute(SessionAttribute.USER); if (verboseDebug()) { // TODO if we have any sort of authentication later, we need to make sure to not log passwords! // I could use getParameterMap, but that returns an array, and getting pretty strings out of // array values is a lot of work. final Map<String, Object> params = new HashMap<String, Object>(); final Enumeration<String> paramNames = request.getParameterNames(); while (paramNames.hasMoreElements()) { final String name = paramNames.nextElement(); params.put(name, request.getParameter(name)); } log(user, "Request: " + JSONValue.toJSONString(params)); } final String op = request.getParameter(AjaxRequest.OP.toString()); // we don't make sure they have a User object if they are doing either of the requests that // create or check for the User object. That would be silly. final boolean skipSessionUserCheck = op != null && (op.equals(AjaxOperation.REGISTER.toString()) || op.equals(AjaxOperation.FIRST_LOAD.toString())); if (!skipSessionUserCheck && hSession.getAttribute(SessionAttribute.USER) == null) { returnError(user, response.getWriter(), ErrorCode.NOT_REGISTERED, serial); } else if (user != null && !user.isValid()) { // user probably pinged out, or possibly kicked by admin hSession.invalidate(); returnError(user, response.getWriter(), ErrorCode.SESSION_EXPIRED, serial); } else { try { handleRequest(request, response, hSession); } catch (final AssertionError ae) { log("Assertion failed", ae); log(user, ae.toString()); } } } catch (final IllegalStateException ise) { // session invalidated, so pretend they don't have one. returnError(null, response.getWriter(), ErrorCode.NO_SESSION, serial); } } /** * @return Whether verbose logging is enabled. */ private boolean verboseDebug() { final Boolean verboseDebugObj = (Boolean) getServletContext().getAttribute( StartupUtils.VERBOSE_DEBUG); final boolean verboseDebug = verboseDebugObj != null ? verboseDebugObj.booleanValue() : false; return verboseDebug; } /** * Handles a request from a CAH client. A session is guaranteed to exist at this point. * * @param request * The request data. * @param response * The response to the client. * @param hSession * The client's session. * @throws ServletException * @throws IOException */ protected abstract void handleRequest(final HttpServletRequest request, final HttpServletResponse response, final HttpSession hSession) throws ServletException, IOException; /** * Return an error to the client. * * @param user * User that caused the error. * @param writer * Response writer to send the error data to. * @param code * Error code to return to client. * @param serial * Request serial number from client. */ @SuppressWarnings("unchecked") protected void returnError(@Nullable final User user, final PrintWriter writer, final ErrorCode code, final int serial) { final JSONObject ret = new JSONObject(); ret.put(AjaxResponse.ERROR, Boolean.TRUE); ret.put(AjaxResponse.ERROR_CODE, code.toString()); ret.put(AjaxResponse.SERIAL, String.valueOf(serial)); writer.println(ret.toJSONString()); } /** * Return response data to the client. * * @param user * User this response is for. * @param writer * Writer for the response. * @param data * Key-value data to return as the response. */ protected void returnData(@Nullable final User user, final PrintWriter writer, final Map<ReturnableData, Object> data) { returnObject(user, writer, data); } /** * Return multiple response data to the client. * * @param user * User this response is for. * @param writer * Writer for the response. * @param data_list * List of key-value data to return as the response. */ protected void returnArray(@Nullable final User user, final PrintWriter writer, final List<Map<ReturnableData, Object>> data_list) { returnObject(user, writer, data_list); } /** * Return any response data to the client. * * @param user * User this response is for. * @param writer * Writer for the response. * @param object * Data to return. */ private void returnObject(@Nullable final User user, final PrintWriter writer, final Object object) { final String ret = JSONValue.toJSONString(object); writer.println(ret); if (verboseDebug()) { log(user, "Response: " + ret); } } /** * @return Guice injector for the servlet context. */ protected Injector getInjector() { return (Injector) getServletContext().getAttribute(StartupUtils.INJECTOR); } /** * Log a message, with the user's name if {@code user} is not null. * * @param user * The user this log message is about, or {@code null} if unknown. * @param message * The message to log. */ protected void log(@Nullable final User user, final String message) { final String userStr; if (user != null) { userStr = user.getNickname(); } else { userStr = "unknown user"; } log("For " + userStr + ": " + message); } }