/*
* Copyright 2007 Zhang, Zheng <oldbig@gmail.com>
*
* This file is part of ZOJ.
*
* ZOJ is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either revision 3 of the License, or (at your option) any later revision.
*
* ZOJ is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with ZOJ. if not, see
* <http://www.gnu.org/licenses/>.
*/
package cn.edu.zju.acm.onlinejudge.action;
import java.util.Date;
import java.util.Enumeration;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.log4j.Logger;
import org.apache.log4j.PropertyConfigurator;
import org.apache.struts.action.Action;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.ActionMapping;
import org.apache.struts.action.ActionMessage;
import org.apache.struts.action.ActionMessages;
import org.apache.struts.action.RedirectingActionForward;
import cn.edu.zju.acm.onlinejudge.bean.AbstractContest;
import cn.edu.zju.acm.onlinejudge.bean.Contest;
import cn.edu.zju.acm.onlinejudge.bean.Course;
import cn.edu.zju.acm.onlinejudge.bean.Problem;
import cn.edu.zju.acm.onlinejudge.bean.UserProfile;
import cn.edu.zju.acm.onlinejudge.bean.enumeration.PermissionLevel;
import cn.edu.zju.acm.onlinejudge.persistence.ContestPersistence;
import cn.edu.zju.acm.onlinejudge.persistence.PersistenceException;
import cn.edu.zju.acm.onlinejudge.security.UserSecurity;
import cn.edu.zju.acm.onlinejudge.util.ContestManager;
import cn.edu.zju.acm.onlinejudge.util.PerformanceManager;
import cn.edu.zju.acm.onlinejudge.util.PersistenceManager;
/**
* BaseAction.
*
* @author Zhang, Zheng
* @version 2.0
*/
public abstract class BaseAction extends Action {
/**
* The enter operation.
*/
private static final String ENTER_OP = "Enter";
/**
* The exit operation.
*/
private static final String EXIT_OP = "Exit";
/**
* The generic error message key.
*/
private static final String GENERIC_ERROR_MESSAGE_KEY = "error";
/**
* The generic error resource key.
*/
private static final String GENERIC_ERROR_RESOURCE_KEY = "onlinejudge.failure";
/**
* The logger name.
*/
private static final String LOGGER_NAME = "cn.edu.zju.acm.onlinejudge";
/**
* The logger.
*/
private static Logger logger = null;
/**
* This is where the action processes the request. It forwards the invocation to the abstract execute() method and
* returns its forward. Unexcepted exceptions are handled.
*
* @param mapping
* the action mapping that holds the forwards.
* @param form
* the form bean for input.
* @param request
* the http servlet request.
* @param response
* the http servlet response.
*
* @return an action forward or null if the response is committed.
*
*/
@Override
public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request,
HttpServletResponse response) {
UserProfile user = (UserProfile) request.getSession().getAttribute(ContextAdapter.USER_PROFILE_SESSION_KEY);
long actionId = PerformanceManager.getInstance().actionStart(this, request, user);
ContextAdapter context = null;
ActionForward forward = null;
try {
context = new ContextAdapter(request, response);
this.info(this.makeInfo(BaseAction.ENTER_OP, context.getOperator(), null, request));
// log parameters with debug level
/*
* debug("Received parameters:"); for (Enumeration enu = request.getParameterNames();
* enu.hasMoreElements();) { String name = (String) enu.nextElement(); debug("[" + name + "]"); for (int i =
* 0; i < request.getParameterValues(name).length; ++i) { debug(" [" + request.getParameterValues(name)[i]
* + "]"); } }
*/
forward = this.execute(mapping, form, context);
} catch (Exception e) {
this.error(e);
forward = this.handleFailure(mapping, context, BaseAction.GENERIC_ERROR_RESOURCE_KEY);
}
PerformanceManager.getInstance().actionEnd(actionId);
return forward;
}
/**
* This is the template method for BaseAction. All the actions in this component implements this metod.
*
* @return an action forward or null if the response is committed.
* @param mapping
* the action mapping that holds the forwards.
* @param form
* the form bean for input.
* @param context
* the action context to access resources.
*
* @throws Exception
* for unexpected errors during exection.
*/
protected abstract ActionForward execute(ActionMapping mapping, ActionForm form, ContextAdapter context) throws Exception;
/**
* Gets the logger.
*
* @return the logger.
*/
private Logger getLogger() {
if (BaseAction.logger == null) {
synchronized (this) {
if (BaseAction.logger == null) {
String fileName = BaseAction.class.getClassLoader().getResource("log4j.properties").getFile();
PropertyConfigurator.configure(fileName);
BaseAction.logger = Logger.getLogger(BaseAction.LOGGER_NAME);
}
}
}
return BaseAction.logger;
}
/**
* This logs a message with a level of DEBUG.
*
* @param message
* the message to log.
*/
protected void debug(String message) {
this.getLogger().debug(message);
}
/**
* This logs a message with a level of INFO.
*
* @param message
* the message to log.
*/
protected void info(String message) {
this.getLogger().info(message);
}
/**
* This logs a message with a level of ERROR.
*
* @param message
* the message to log.
*/
protected void error(String message) {
this.getLogger().error(message);
}
/**
* This logs an exception's stack trace with a level of ERROR.
*
* @param exception
* the exception to log.
*/
protected void error(Throwable exception) {
this.getLogger().error(null, exception);
}
/**
* Provides convenience method to handle errors.
*
* @param mapping
* the action mapping that holds the forwards.
* @param request
* the http servlet request.
* @param messageKey
* the resource key to retrieve the error message.
*
* @return the failure forward.
*/
protected ActionForward handleFailure(ActionMapping mapping, ContextAdapter context, String resourceKey) {
return this.handleFailure(mapping, context, BaseAction.GENERIC_ERROR_MESSAGE_KEY, resourceKey);
}
/**
* Provides convenience method to handle errors.
*
* @param mapping
* the action mapping that holds the forwards.
* @param request
* the http servlet request.
* @param messageKey
* the resource key to retrieve the error message.
* @param errorKey
* the error key.
*
* @return the failure forward.
*/
protected ActionForward handleFailure(ActionMapping mapping, ContextAdapter context, String errorKey,
String resourceKey) {
this.info(this.makeInfo(BaseAction.EXIT_OP, context.getOperator(), "failure"));
ActionMessages errors = new ActionMessages();
errors.add(errorKey, new ActionMessage(resourceKey));
this.saveErrors(context.getRequest(), errors);
return mapping.findForward("failure");
}
/**
* Provides convenience method to handle errors.
*
* @param mapping
* the action mapping that holds the forwards.
* @param request
* the http servlet request.
* @param errors
* ActionMessages
*
* @return the failure forward.
*/
protected ActionForward handleFailure(ActionMapping mapping, ContextAdapter context, ActionMessages errors,
String forwardName) {
this.info(this.makeInfo(BaseAction.EXIT_OP, context.getOperator(), forwardName));
this.saveErrors(context.getRequest(), errors);
return mapping.findForward(forwardName);
}
/**
* Provides convenience method to handle errors.
*
* @param mapping
* the action mapping that holds the forwards.
* @param request
* the http servlet request.
* @param errors
* ActionMessages
*
* @return the failure forward.
*/
protected ActionForward handleFailure(ActionMapping mapping, ContextAdapter context, ActionMessages errors) {
return this.handleFailure(mapping, context, errors, "failure");
}
/**
* Handle successful exit with log. Helper method.
*
* @param mapping
* the action mapping that holds the forwards.
* @param context
* the action context to access resources.
* @param forwardName
* represents messsageKey if fail, forward name if succeed.
*
* @return the specified forward.
*/
protected ActionForward handleSuccess(ActionMapping mapping, ContextAdapter context) {
return this.handleSuccess(mapping.getInputForward(), context, mapping.getInput());
}
/**
* Handle successful exit with log. Helper method.
*
* @param mapping
* the action mapping that holds the forwards.
* @param context
* the action context to access resources.
* @param forwardName
* represents messsageKey if fail, forward name if succeed.
*
* @return the specified forward.
*/
protected ActionForward handleSuccess(ActionMapping mapping, ContextAdapter context, String forwardName) {
return this.handleSuccess(mapping.findForward(forwardName), context, forwardName);
}
/**
* Handle successful exit with log. Helper method.
*
* @param mapping
* the action mapping that holds the forwards.
* @param context
* the action context to access resources.
* @param forwardName
* represents messsageKey if fail, forward name if succeed.
*
* @return the specified forward.
*/
protected ActionForward handleSuccess(ActionMapping mapping, ContextAdapter context, String forwardName,
String parameter) {
String newPath = mapping.findForward(forwardName).getPath() + parameter;
ActionForward forward = new RedirectingActionForward(newPath);
return this.handleSuccess(forward, context, forwardName);
}
/**
* Handle successful exit with log. Helper method.
*
* @param forward
* the forward.
* @param context
* the action context to access resources.
* @param forwardName
* represents messsageKey if fail, forward name if succeed.
*
* @return the specified forward.
*/
protected ActionForward handleSuccess(ActionForward forward, ContextAdapter context, String forwardName) {
this.info(this.makeInfo(BaseAction.EXIT_OP, context.getOperator(), forwardName));
return forward;
}
/**
* Synthesize information for entrance or exit in each action. Helper method.
*
* @param operation
* "Enter" or "Exit".
* @param actionType
* type of action.
* @param user
* the user.
* @param forward
* the action forward string.
*
* @return the synthesized information.
*/
private String makeInfo(String operation, Object user, String forward) {
return this.makeInfo(operation, user, forward, null);
}
/**
* Synthesize information for entrance or exit in each action. Helper method.
*
* @param operation
* "Enter" or "Exit".
* @param actionType
* type of action.
* @param user
* the user.
* @param forward
* the action forward string.
*
* @return the synthesized information.
*/
private String makeInfo(String operation, Object user, String forward, HttpServletRequest request) {
String actionType = this.getClass().getName();
actionType = actionType.substring(actionType.lastIndexOf(".") + 1);
StringBuffer buffer = new StringBuffer();
buffer.append(operation);
buffer.append(" ").append(actionType);
buffer.append(" with user = ").append(user);
if (forward != null) {
buffer.append(", action forward to ").append(forward);
}
buffer.append(", timestamp = ").append(new Date()).append(". ");
if (request != null) {
buffer.append(request.getRemoteHost());
}
return buffer.toString();
}
/**
* Checks whether user is logged in.
*
* @return
*/
protected boolean isLogin(ContextAdapter context) {
return this.isLogin(context, false);
}
/**
* Checks whether user is logged in.
*
* @return
*/
protected boolean isLogin(ContextAdapter context, boolean includeParameters) {
if (context.getUserProfile() == null) {
String uri = context.getRequest().getServletPath();
if (includeParameters) {
StringBuffer sb = new StringBuffer();
sb.append(uri);
Enumeration<String> e = context.getRequest().getParameterNames();
boolean first = true;
while (e.hasMoreElements()) {
String key = e.nextElement();
if ("source".equals(key)) {
continue;
}
if (first) {
sb.append("?");
first = false;
} else {
sb.append("&");
}
sb.append(key + "=" + context.getRequest().getParameter(key));
}
uri = sb.toString();
}
context.setAttribute("forward", uri);
return false;
}
return true;
}
protected ActionForward checkContestViewPermission(ActionMapping mapping, ContextAdapter context,
Boolean isProblemset, boolean checkStart) throws Exception {
return this.checkContestPermission(mapping, context, isProblemset, checkStart, PermissionLevel.VIEW);
}
protected ActionForward checkContestParticipatePermission(ActionMapping mapping, ContextAdapter context,
Boolean isProblemset, boolean checkStart) throws Exception {
return this.checkContestPermission(mapping, context, isProblemset, checkStart, PermissionLevel.PARTICIPATE);
}
protected ActionForward checkContestAdminPermission(ActionMapping mapping, ContextAdapter context,
Boolean isProblemset, boolean checkStart) throws Exception {
return this.checkContestPermission(mapping, context, isProblemset, checkStart, PermissionLevel.ADMIN);
}
protected ActionForward checkContestViewSourcePermission(ActionMapping mapping, ContextAdapter context,
Boolean isProblemset, boolean checkStart) throws Exception {
return this.checkContestPermission(mapping, context, isProblemset, checkStart,
PermissionLevel.PARTICIPATECANVIEWSOURCE);
}
protected ActionForward checkContestPermission(ActionMapping mapping, ContextAdapter context, Boolean isProblemset,
boolean checkStart, PermissionLevel level) throws Exception {
// get the contest
AbstractContest contest = context.getContest();
if (contest == null || isProblemset != null && (contest instanceof Contest || contest instanceof Course) == isProblemset.booleanValue()) {
context.setAttribute("contest", null);
ActionMessages messages = new ActionMessages();
messages.add("message", new ActionMessage("onlinejudge.showcontest.nocontestid"));
this.saveErrors(context.getRequest(), messages);
if (isProblemset != null) {
context.setAttribute("back", isProblemset ? "showProblemsets.do" : "showContests.do");
}
return this.handleFailure(mapping, context, messages, "nopermission");
}
context.setAttribute("contest", contest);
// check contest permission
UserSecurity userSecurity = context.getUserSecurity();
boolean hasPermisstion = false;
if (level == PermissionLevel.ADMIN) {
hasPermisstion = userSecurity.canAdminContest(contest.getId());
} else if (level == PermissionLevel.PARTICIPATE) {
hasPermisstion = userSecurity.canParticipateContest(contest.getId());
} else if (level == PermissionLevel.VIEW) {
hasPermisstion = userSecurity.canViewContest(contest.getId());
} else if (level == PermissionLevel.PARTICIPATECANVIEWSOURCE) {
hasPermisstion = userSecurity.canViewSource(contest.getId());
}
if (!hasPermisstion) {
ActionMessages messages = new ActionMessages();
messages.add("message", new ActionMessage("onlinejudge.showcontest.nopermission"));
this.saveErrors(context.getRequest(), messages);
if (isProblemset != null) {
context.setAttribute("back", isProblemset ? "showProblemsets.do" : "showContests.do");
}
return this.handleFailure(mapping, context, messages, "nopermission");
}
// check start time
if (checkStart && !userSecurity.canAdminContest(contest.getId())) {
return this.checkContestStart(mapping, context, contest);
}
return null;
}
protected ActionForward checkProblemViewPermission(ActionMapping mapping, ContextAdapter context,
Boolean isProblemset) throws Exception {
return this.checkProblemPermission(mapping, context, isProblemset, PermissionLevel.VIEW);
}
protected ActionForward checkProblemParticipatePermission(ActionMapping mapping, ContextAdapter context,
Boolean isProblemset) throws Exception {
return this.checkProblemPermission(mapping, context, isProblemset, PermissionLevel.PARTICIPATE);
}
protected ActionForward checkProblemAdminPermission(ActionMapping mapping, ContextAdapter context,
Boolean isProblemset) throws Exception {
return this.checkProblemPermission(mapping, context, isProblemset, PermissionLevel.ADMIN);
}
protected ActionForward checkProblemViewSourecPermission(ActionMapping mapping, ContextAdapter context,
Boolean isProblemset) throws Exception {
return this.checkProblemPermission(mapping, context, isProblemset, PermissionLevel.PARTICIPATECANVIEWSOURCE);
}
protected ActionForward checkProblemPermission(ActionMapping mapping, ContextAdapter context, Boolean isProblemset,
PermissionLevel level) throws Exception {
Problem problem = context.getProblem();
AbstractContest contest = null;
if (problem != null) {
contest = ContestManager.getInstance().getContest(problem.getContestId());
}
if (problem == null || contest == null || isProblemset != null &&
(contest instanceof Contest || contest instanceof Course) == isProblemset.booleanValue()) {
ActionMessages messages = new ActionMessages();
messages.add("message", new ActionMessage("onlinejudge.showproblem.noproblemid"));
this.saveErrors(context.getRequest(), messages);
if (isProblemset != null) {
context.setAttribute("back", isProblemset ? "showProblemsets.do" : "showContests.do");
}
return this.handleFailure(mapping, context, messages, "nopermission");
}
context.setAttribute("contest", contest);
context.setAttribute("problem", problem);
// check contest permission
UserSecurity userSecurity = context.getUserSecurity();
boolean hasPermisstion = false;
if (level == PermissionLevel.ADMIN) {
hasPermisstion = userSecurity.canAdminContest(contest.getId());
} else if (level == PermissionLevel.PARTICIPATE) {
hasPermisstion = userSecurity.canParticipateContest(contest.getId());
} else if (level == PermissionLevel.PARTICIPATECANVIEWSOURCE) {
hasPermisstion = userSecurity.canViewSource(contest.getId());
} else if (level == PermissionLevel.VIEW) {
hasPermisstion = userSecurity.canViewContest(contest.getId());
}
if (!hasPermisstion) {
ActionMessages messages = new ActionMessages();
messages.add("message", new ActionMessage("onlinejudge.showcontest.nopermission"));
this.saveErrors(context.getRequest(), messages);
if (isProblemset != null) {
context.setAttribute("back", isProblemset ? "showProblemsets.do" : "showContests.do");
}
return this.handleFailure(mapping, context, messages, "nopermission");
}
// check start time
if (userSecurity.canAdminContest(contest.getId())) {
return null;
} else {
return this.checkContestStart(mapping, context, contest);
}
}
private ActionForward checkContestStart(ActionMapping mapping, ContextAdapter context, AbstractContest contest) throws PersistenceException {
if (contest.getStartTime() == null) {
return null;
}
if (contest.getStartTime().getTime() > System.currentTimeMillis()) {
ActionMessages messages = new ActionMessages();
messages.add("message", new ActionMessage("onlinejudge.showcontest.nostarted"));
this.saveErrors(context.getRequest(), messages);
context.setAttribute("back", "contestInfo.do?contestId=" + contest.getId());
return this.handleFailure(mapping, context, messages, "nopermission");
}
return null;
}
protected ActionForward checkAdmin(ActionMapping mapping, ContextAdapter context) throws Exception {
UserSecurity security = context.getUserSecurity();
if (security == null || !security.isSuperAdmin()) {
return this.handleSuccess(mapping, context, "nopermission");
}
return null;
}
protected ActionForward checkLastLoginIP(ActionMapping mapping, ContextAdapter context, boolean isProblemset) throws Exception {
String ip = context.getRequest().getRemoteHost();
long contestId = context.getContest().getId();
String ipSessionKey = "last_submit_ip" + contestId;
String lastIp = (String) context.getSessionAttribute(ipSessionKey);
if (lastIp == null) {
ContestPersistence contestPersistence = PersistenceManager.getInstance().getContestPersistence();
long userId = context.getUserProfile().getId();
lastIp = contestPersistence.getLastSubmitIP(userId, contestId);
if (lastIp == null) {
// first submit
contestPersistence.setLastSubmitIP(userId, contestId, ip);
context.setSessionAttribute(ipSessionKey, lastIp);
return null;
}
context.setSessionAttribute(ipSessionKey, lastIp);
}
if (!lastIp.equals(ip)) {
ActionMessages messages = new ActionMessages();
messages.add("message", new ActionMessage("onlinejudge.submit.invalid_ip"));
this.saveErrors(context.getRequest(), messages);
context.setAttribute("back", "contestInfo.do?contestId=" + contestId);
return this.handleFailure(mapping, context, messages, "nopermission");
}
return null;
}
}