package fi.internetix.smvc.dispatcher;
import java.util.HashMap;
import java.util.Map;
import javax.annotation.Resource;
import javax.servlet.ServletConfig;
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 javax.transaction.UserTransaction;
import org.apache.commons.lang.StringUtils;
import fi.internetix.smvc.AccessDeniedException;
import fi.internetix.smvc.LoginRequiredException;
import fi.internetix.smvc.PageNotFoundException;
import fi.internetix.smvc.Severity;
import fi.internetix.smvc.SmvcRuntimeException;
import fi.internetix.smvc.StatusCode;
import fi.internetix.smvc.controllers.BinaryRequestContext;
import fi.internetix.smvc.controllers.BinaryRequestController;
import fi.internetix.smvc.controllers.JSONRequestContext;
import fi.internetix.smvc.controllers.JSONRequestController;
import fi.internetix.smvc.controllers.PageController;
import fi.internetix.smvc.controllers.PageRequestContext;
import fi.internetix.smvc.controllers.RequestContext;
import fi.internetix.smvc.controllers.RequestController;
import fi.internetix.smvc.controllers.RequestControllerMapper;
import fi.internetix.smvc.logging.Logging;
/** The servlet responsible for processing SMVCJ application requests.
*
*/
public class Servlet extends HttpServlet {
@Override
public void init(ServletConfig config) throws ServletException {
super.init(config);
loginUrl = config.getInitParameter("loginUrl");
if (!StringUtils.isBlank(loginUrl)) {
if ("true".equals(config.getInitParameter("loginUrlRelative"))) {
loginUrl = config.getServletContext().getContextPath() + "/" + loginUrl;
}
}
decodeGETUtf = "true".equals(config.getInitParameter("decodeGETUtf"));
String requestDispatcherClass = config.getInitParameter("requestDispatcher");
if (requestDispatcherClass != null) {
try {
requestDispatcher = (RequestDispatcher) Class.forName(requestDispatcherClass).newInstance();
}
catch (Exception e) {
Logging.logException(e);
throw new ServletException("Failed to instantiate request dispatcher " + requestDispatcherClass, e);
}
}
String platformErrorListenerClass = config.getInitParameter("platformErrorListener");
if (platformErrorListenerClass != null) {
try {
platformErrorListener = (PlatformErrorListener) Class.forName(platformErrorListenerClass).newInstance();
} catch (Exception e) {
Logging.logException(e);
throw new ServletException("Failed to instantiate platform error listener " + platformErrorListenerClass, e);
}
}
errorJspPage = config.getInitParameter("errorJspPage");
applicationPath = config.getInitParameter("applicationPath");
sessionSynchronization = "true".equals(config.getInitParameter("sessionSynchronization"));
}
/**
* Processes all application requests, delegating them to their corresponding page, binary and JSON controllers.
*/
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, java.io.IOException {
if (sessionSynchronization) {
String syncKey = getSyncKey(request);
synchronized (getSyncObject(syncKey)) {
try {
doService(request, response);
}
finally {
removeSyncObject(syncKey);
}
}
}
else {
doService(request, response);
}
}
private synchronized Object getSyncObject(String syncKey) {
Object syncObject = syncObjects.get(syncKey);
if (syncObject == null) {
syncObject = new Object();
syncObjects.put(syncKey, syncObject);
}
return syncObject;
}
private synchronized void removeSyncObject(String syncKey) {
syncObjects.remove(syncKey);
}
private String getSyncKey(HttpServletRequest request) {
StringBuilder sb = new StringBuilder();
sb.append(request.getSession(true).getId());
sb.append(getCurrentUrl(request, false));
sb.append(request.getMethod());
return sb.toString();
}
private void doService(HttpServletRequest request,
HttpServletResponse response) throws ServletException {
// Start a transaction for the request
try {
userTransaction.begin();
} catch (Exception e) {
Logging.logException(e);
throw new ServletException(e);
}
RequestContext requestContext = null;
RequestController requestController;
RequestDispatchContext dispatchContext;
if (requestDispatcher != null && requestDispatcher.canHandle(request, response)) {
dispatchContext = requestDispatcher.getContext(request, response);
requestController = dispatchContext.getRequestController();
}
else {
String uri = request.getRequestURI();
String ctxPath = request.getContextPath();
String controllerName = uri.substring(ctxPath.length() + 1);
if (StringUtils.isNotBlank(applicationPath)) {
controllerName = controllerName.substring(applicationPath.length());
}
requestController = RequestControllerMapper.getRequestController(controllerName);
dispatchContext = new RequestDispatchContext(requestController, new DefaultParameterHandlerImpl(request, decodeGETUtf));
}
int statusCode = StatusCode.OK;
try {
// Determine suitable request context based on the controller type
if (requestController == null) {
requestContext = new PageRequestContext(dispatchContext, request, response, getServletContext(), errorJspPage);
throw new PageNotFoundException(request.getLocale());
}
else if (requestController instanceof PageController) {
requestContext = new PageRequestContext(dispatchContext, request, response, getServletContext(), errorJspPage);
}
else if (requestController instanceof JSONRequestController) {
requestContext = new JSONRequestContext(dispatchContext, request, response, getServletContext());
}
else if (requestController instanceof BinaryRequestController) {
requestContext = new BinaryRequestContext(dispatchContext, request, response, getServletContext());
}
// Let the controller authorize the request. Most common exceptions thrown include
// LoginRequiredException and AccessDeniedException
requestController.authorize(requestContext);
// Request has been authorized, so the controller is asked to process it
if (requestController instanceof PageController) {
((PageController) requestController).process((PageRequestContext) requestContext);
}
else if (requestController instanceof JSONRequestController) {
((JSONRequestController) requestController).process((JSONRequestContext) requestContext);
}
else if (requestController instanceof BinaryRequestController) {
((BinaryRequestController) requestController).process((BinaryRequestContext) requestContext);
}
}
catch (LoginRequiredException lre) {
if (platformErrorListener != null)
platformErrorListener.onLoginRequiredException(request, response, lre);
Logging.logInfo("Login required for " + getCurrentUrl(request, true));
if (requestController instanceof PageController) {
HttpSession session = requestContext.getRequest().getSession(true);
session.setAttribute("loginRedirectUrl", lre.getRedirectUrl());
if (lre.getContextType() != null && lre.getContextId() != null) {
session.setAttribute("loginContextType", lre.getContextType());
session.setAttribute("loginContextId", lre.getContextId());
}
requestContext.setRedirectURL(loginUrl);
}
else {
// TODO LoginRequiredException for requests other than pages?
statusCode = lre.getStatusCode();
requestContext.addMessage(Severity.WARNING, lre.getMessage());
}
}
catch (PageNotFoundException pnfe) {
if (platformErrorListener != null)
platformErrorListener.onPageNotFoundException(request, response, pnfe);
Logging.logInfo("404 - " + getCurrentUrl(request, true));
statusCode = pnfe.getStatusCode();
if (requestContext != null) {
requestContext.getResponse().setStatus(HttpServletResponse.SC_NOT_FOUND);
requestContext.addMessage(Severity.WARNING, pnfe.getMessage());
} else {
response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
}
}
catch (AccessDeniedException ade) {
if (platformErrorListener != null)
platformErrorListener.onAccessDeniedException(request, response, ade);
Logging.logInfo("403 - " + getCurrentUrl(request, true) + " - " + requestContext.getLoggedUserId());
statusCode = ade.getStatusCode();
requestContext.getResponse().setStatus(HttpServletResponse.SC_FORBIDDEN);
requestContext.addMessage(Severity.WARNING, ade.getMessage());
}
catch (SmvcRuntimeException pre) {
if (platformErrorListener != null)
platformErrorListener.onSmvcRuntimeException(request, response, pre);
Logging.logException(pre);
statusCode = pre.getStatusCode();
requestContext.addMessage(Severity.ERROR, pre.getMessage());
}
catch (Exception e) {
if (platformErrorListener != null)
platformErrorListener.onUncontrolledException(request, response, e);
// All other exceptions are considered to be fatal and unexpected, so the request
// transaction is rolled back, the stack trace of the exception is printed out, and
// an error view is shown
Logging.logException(e);
statusCode = StatusCode.UNDEFINED;
requestContext.addMessage(Severity.CRITICAL, e.getMessage());
}
finally {
try {
// Pre-commit response
requestContext.writePreCommitResponse(statusCode);
// Request complete, so commit or rollback based on the status of the request
if (statusCode == StatusCode.OK) {
userTransaction.commit();
} else {
userTransaction.rollback();
}
// Post-commit response
requestContext.writePostCommitResponse(statusCode);
}
catch (Exception e) {
if (platformErrorListener != null)
platformErrorListener.onTransactionCommitException(request, response, e);
Logging.logException(e);
throw new ServletException(e);
}
}
}
private String getBaseUrl(HttpServletRequest request) {
String currentURL = request.getRequestURL().toString();
String pathInfo = request.getRequestURI();
return currentURL.substring(0, currentURL.length() - pathInfo.length());
}
private String getCurrentUrl(HttpServletRequest request, boolean stripApp) {
if (stripApp == false) {
StringBuilder currentUrl = new StringBuilder(request.getRequestURL());
String queryString = request.getQueryString();
if (!StringUtils.isBlank(queryString)) {
currentUrl.append('?');
currentUrl.append(queryString);
}
return currentUrl.toString();
} else {
StringBuilder currentUrl = new StringBuilder(getBaseUrl(request));
String contextPath = request.getContextPath();
currentUrl.append(contextPath);
String pathInfo = request.getRequestURI().substring(contextPath.length());
if (applicationPath != null && pathInfo.startsWith(applicationPath)) {
pathInfo = pathInfo.substring(applicationPath.length());
}
currentUrl.append(pathInfo);
String queryString = request.getQueryString();
if (!StringUtils.isBlank(queryString)) {
currentUrl.append('?');
currentUrl.append(queryString);
}
return currentUrl.toString();
}
}
private boolean decodeGETUtf = false;
private String loginUrl = "";
private String errorJspPage = "";
private String applicationPath = "";
private RequestDispatcher requestDispatcher;
private PlatformErrorListener platformErrorListener;
private boolean sessionSynchronization = false;
private Map<String, Object> syncObjects = new HashMap<>();
private static final long serialVersionUID = 1L;
@Resource
private UserTransaction userTransaction;
}