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