com.jsmartframework.web.manager.ServletControl.java Source code

Java tutorial

Introduction

Here is the source code for com.jsmartframework.web.manager.ServletControl.java

Source

/*
 * JSmart Framework - Java Web Development Framework
 * Copyright (c) 2015, Jeferson Albino da Silva, All rights reserved.
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 3.0 of the License, or (at your option) any later version.
 * 
 * This library 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
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
*/

package com.jsmartframework.web.manager;

import static com.jsmartframework.web.config.Constants.REQUEST_REDIRECT_PATH_AJAX_ATTR;
import static com.jsmartframework.web.config.Constants.REQUEST_REDIRECT_WINDOW_PATH_AJAX_ATTR;
import static com.jsmartframework.web.manager.BeanHandler.HANDLER;
import static com.jsmartframework.web.config.Constants.ENCODING;
import static com.jsmartframework.web.config.Constants.NEXT_URL;

import com.jsmartframework.web.listener.WebAsyncListener;
import com.jsmartframework.web.listener.WebAsyncListener.Reason;
import com.jsmartframework.web.util.WebUtils;
import org.apache.commons.lang.StringUtils;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.servlet.AsyncContext;
import javax.servlet.AsyncEvent;
import javax.servlet.AsyncListener;
import javax.servlet.ServletConfig;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public final class ServletControl extends HttpServlet {

    private static final long serialVersionUID = -4462762772195421585L;

    private static final Logger LOGGER = Logger.getLogger(ServletControl.class.getPackage().getName());

    @Override
    public void init(ServletConfig servletConfig) throws ServletException {
        super.init(servletConfig);
        WebContext.setServlet(this);

        // Call registered WebContextListeners
        for (ServletContextListener contextListener : HANDLER.contextListeners) {
            HANDLER.executeInjection(contextListener);
            contextListener.contextInitialized(new ServletContextEvent(servletConfig.getServletContext()));
        }
    }

    @Override
    public void destroy() {
        // Call registered WebContextListeners
        for (ServletContextListener contextListener : HANDLER.contextListeners) {
            contextListener.contextDestroyed(new ServletContextEvent(getServletContext()));
        }
        super.destroy();
    }

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        String path = request.getServletPath();

        // If path is secure, check if user was logged case @AuthBean annotation was provided
        if (checkAuthentication(path, request, response)) {
            return;
        }
        // Return if request is for async bean handling
        if (doAsync(path, request, response)) {
            return;
        }
        // If got here the request is for web bean handling
        sendForward(path, request, response);
    }

    @Override
    @SuppressWarnings("all")
    protected void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        String path = request.getServletPath();

        // If path is secure, check if user was logged case @AuthBean annotation was provided
        if (checkAuthentication(path, request, response)) {
            return;
        }

        // Check if user is authorized to access the page. Send HTTP 403 response case they did not have
        Integer httpStatus = HANDLER.checkAuthorization(path);
        if (httpStatus != null) {
            LOGGER.log(Level.INFO, "Access not authorized on page [" + path + "]");
            response.sendError(httpStatus);
            return;
        }

        // Check if user is truly valid by carrying CSRF token if implemented
        httpStatus = HANDLER.checkWebSecurityToken(request);
        if (httpStatus != null) {
            LOGGER.log(Level.INFO, "Possibly invalid access via CSRF attack on page [" + path + "]");
            response.sendError(httpStatus);
            return;
        }

        // Decrypt expressions if needed
        Map<String, String> expressions = HANDLER.getRequestExpressions(request);

        // Initiate beans mentioned on jsp page (Case request scope beans)
        try {
            HANDLER.instantiateBeans(path, expressions);
        } catch (Exception ex) {
            LOGGER.log(Level.INFO, "WebBeans on page [" + path + "] could not be instantiated: " + ex.getMessage());
            throw new ServletException(ex);
        }

        // Case user had ordered redirect to specific path in postConstruct method
        String redirectPath = WebContext.getRedirectTo();
        if (redirectPath != null && !redirectPath.equals(path)) {
            sendRedirect(redirectPath, request, response);
            return;
        }

        boolean redirectAjax = false;
        String responsePath = HANDLER.handleRequestExpressions(expressions);

        // Case response was written directly, just return
        if (WebContext.isResponseWritten()) {
            return;
        }

        // Decode the response path case returned from action method
        if (responsePath != null) {
            responsePath = WebUtils.decodePath(responsePath);
        }

        // Case user had ordered redirect to specific path in submitted method
        redirectPath = WebContext.getRedirectTo();
        if (redirectPath != null && !redirectPath.equals(path)) {
            responsePath = redirectPath;
        }

        // Case is Ajax post action and submit method returned a path, let JavaScript redirect page
        if (responsePath != null && "XMLHttpRequest".equals(request.getHeader("X-Requested-With"))) {
            redirectAjax = true;
        }

        if (responsePath == null) {
            responsePath = path;
        } else {

            // Case is Ajax post action, let JavaScript redirect page
            if ("XMLHttpRequest".equals(request.getHeader("X-Requested-With"))) {
                if (redirectAjax) {
                    request.setAttribute(REQUEST_REDIRECT_PATH_AJAX_ATTR,
                            getRedirectPath(responsePath, request, false));
                    request.setAttribute(REQUEST_REDIRECT_WINDOW_PATH_AJAX_ATTR, WebContext.isRedirectToWindow());
                }
                responsePath = path;
            }
        }
        sendRedirect(responsePath, request, response);
    }

    private boolean checkAuthentication(String path, HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        AuthPath authPath = HANDLER.checkAuthentication(path);
        if (authPath.shouldRedirectFromPath(path)) {
            sendRedirect(authPath.getPath(), request, response, !authPath.isHomePath());
            return true;
        }
        return false;
    }

    private boolean doAsync(String path, HttpServletRequest request, HttpServletResponse response)
            throws IOException, ServletException {
        try {
            // Only proceed if the AsyncContext was not started to avoid looping whe dispatch is called
            if (!request.isAsyncStarted()) {
                WebAsyncListener bean = (WebAsyncListener) HANDLER.instantiateAsyncBean(path);

                if (bean != null) {
                    AsyncContext asyncContext = request.startAsync();
                    bean.asyncContextCreated(asyncContext);
                    asyncContext.addListener(new WebServletAsyncListener(path, bean));
                    return true;
                }
            }
        } catch (Exception ex) {
            LOGGER.log(Level.SEVERE,
                    "AsyncBean on path [" + path + "] could not be instantiated: " + ex.getMessage());
            throw new ServletException(ex);
        }
        return false;
    }

    private void sendForward(String path, HttpServletRequest request, HttpServletResponse response)
            throws IOException, ServletException {

        // Check if user is authorized to access the page. Send HTTP 403 response case they did not have
        Integer httpStatus = HANDLER.checkAuthorization(path);
        if (httpStatus != null) {
            LOGGER.log(Level.INFO, "WebBean access not authorized on page [" + path + "]");
            response.sendError(httpStatus);
            return;
        }

        // Initiate beans mentioned on jsp page
        try {
            HANDLER.instantiateBeans(path, null);
        } catch (Exception ex) {
            LOGGER.log(Level.SEVERE,
                    "WebBeans on page [" + path + "] could not be instantiated: " + ex.getMessage());
            throw new ServletException(ex);
        }

        // Case user had ordered redirect to specific path in postConstruct method
        String redirectPath = WebContext.getRedirectTo();
        if (redirectPath != null && !redirectPath.equals(path)) {
            sendRedirect(redirectPath, request, response);
            return;
        }

        // Case response was written directly, just return
        if (WebContext.isResponseWritten()) {
            return;
        }

        // Case is Ajax post action, let JavaScript redirect page
        if ("XMLHttpRequest".equals(request.getHeader("X-Requested-With"))) {
            request.setAttribute(REQUEST_REDIRECT_PATH_AJAX_ATTR, getRedirectPath(path, request, false));
            request.setAttribute(REQUEST_REDIRECT_WINDOW_PATH_AJAX_ATTR, WebContext.isRedirectToWindow());
        }

        // Use Forward request internally case is the same page
        String url = HANDLER.getForwardPath(path);
        if (url == null) {
            LOGGER.log(Level.SEVERE, "Could not find JSP page for path [" + path + "]");
            return;
        }

        // Generate web security token to prevent CSRF attack
        HANDLER.generateWebSecurityToken(request, response);

        // Use Forward request internally case is the same page
        request.getRequestDispatcher(url).forward(request, response);
    }

    private void sendRedirect(String path, HttpServletRequest request, HttpServletResponse response)
            throws IOException, ServletException {
        sendRedirect(path, request, response, false);
    }

    private void sendRedirect(String path, HttpServletRequest request, HttpServletResponse response,
            boolean authNeeded) throws IOException, ServletException {
        if (request.getServletPath().equals(path)) {
            String url = HANDLER.getForwardPath(path);
            if (url == null) {
                LOGGER.log(Level.SEVERE, "Could not find JSP page for path [" + path + "]");
                return;
            }

            // Generate web security token to prevent CSRF attack
            HANDLER.generateWebSecurityToken(request, response);

            // Use Forward request internally case is the same page
            request.getRequestDispatcher(url).forward(request, response);

        } else {
            // Use Redirect response case page had changed (Do not use status 302 once cookies are not set)
            response.setStatus(HttpServletResponse.SC_TEMPORARY_REDIRECT);
            response.setHeader("Location", getRedirectPath(path, request, authNeeded));
        }
    }

    private String getRedirectPath(String path, HttpServletRequest request, boolean authNeeded) {
        StringBuilder nextUrl = new StringBuilder();

        if (authNeeded) {
            nextUrl.append("?").append(NEXT_URL).append("=");

            if (!request.getContextPath().equals("/")) {
                nextUrl.append(request.getContextPath());
            }
            nextUrl.append(request.getServletPath());

            if (StringUtils.isNotBlank(request.getPathInfo())) {
                nextUrl.append(request.getPathInfo());
            }
            if (StringUtils.isNotBlank(request.getQueryString())) {
                nextUrl.append(encodeUrlQuietly("?" + request.getQueryString()));
            }
        } else {
            String nextPath = request.getParameter(NEXT_URL);
            if (StringUtils.isNotBlank(nextPath)) {
                return nextPath;
            }
        }
        return (path.startsWith("/") ? request.getContextPath() : "") + path + nextUrl;
    }

    private String encodeUrlQuietly(String url) {
        try {
            return URLEncoder.encode(url, ENCODING);
        } catch (UnsupportedEncodingException e) {
            return "";
        }
    }

    private class WebServletAsyncListener implements AsyncListener {

        private String path;

        private WebAsyncListener bean;

        public WebServletAsyncListener(String path, WebAsyncListener bean) {
            this.path = path;
            this.bean = bean;
        }

        @Override
        public void onComplete(AsyncEvent event) throws IOException {
            finalizeAsyncContext(event, Reason.COMPLETE);
        }

        @Override
        public void onTimeout(AsyncEvent event) throws IOException {
            finalizeAsyncContext(event, Reason.TIMEOUT);
        }

        @Override
        public void onError(AsyncEvent event) throws IOException {
            finalizeAsyncContext(event, Reason.ERROR);
        }

        @Override
        public void onStartAsync(AsyncEvent event) throws IOException {
            try {
                bean = (WebAsyncListener) HANDLER.instantiateAsyncBean(path);
                bean.asyncContextCreated(event.getAsyncContext());
            } catch (Exception ex) {
                LOGGER.log(Level.SEVERE,
                        "AsyncBean on path [" + path + "] could not be instantiated: " + ex.getMessage());
            }
        }

        private void finalizeAsyncContext(AsyncEvent event, Reason reason) throws IOException {
            AsyncContext asyncContext = event.getAsyncContext();
            bean.asyncContextDestroyed(asyncContext, reason);
            HANDLER.finalizeAsyncBean(bean, (HttpServletRequest) asyncContext.getRequest());
        }
    }
}