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

Java tutorial

Introduction

Here is the source code for com.jsmartframework.web.manager.BeanHandler.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.Config.CONFIG;
import static com.jsmartframework.web.config.Constants.REQUEST_EXPOSE_VARS_ATTR;
import static com.jsmartframework.web.manager.ExpressionHandler.EXPRESSIONS;
import static com.jsmartframework.web.manager.ExpressionHandler.EL_PATTERN;
import static com.jsmartframework.web.manager.ExpressionHandler.ID_PATTERN;
import static com.jsmartframework.web.manager.ExpressionHandler.EL_PATTERN_FORMAT;
import static com.jsmartframework.web.manager.ExpressionHandler.BEAN_METHOD_NAME_FORMAT;
import static com.jsmartframework.web.manager.ExpressionHandler.JSP_PATTERN;
import static com.jsmartframework.web.manager.TagHandler.J_TAG_PATTERN;
import static com.jsmartframework.web.manager.BeanHelper.HELPER;

import com.jsmartframework.web.adapter.CsrfAdapter;
import com.jsmartframework.web.annotation.Arg;
import com.jsmartframework.web.annotation.AsyncBean;
import com.jsmartframework.web.annotation.AuthBean;
import com.jsmartframework.web.annotation.AuthField;
import com.jsmartframework.web.annotation.AuthType;
import com.jsmartframework.web.annotation.ExecuteAccess;
import com.jsmartframework.web.annotation.ExposeVar;
import com.jsmartframework.web.annotation.Function;
import com.jsmartframework.web.annotation.PostAction;
import com.jsmartframework.web.annotation.PostSubmit;
import com.jsmartframework.web.annotation.PreAction;
import com.jsmartframework.web.annotation.PreSubmit;
import com.jsmartframework.web.annotation.ProduceType;
import com.jsmartframework.web.annotation.QueryParam;
import com.jsmartframework.web.annotation.RequestPath;
import com.jsmartframework.web.annotation.ScopeType;
import com.jsmartframework.web.annotation.Action;
import com.jsmartframework.web.annotation.WebBean;
import com.jsmartframework.web.annotation.WebFilter;
import com.jsmartframework.web.annotation.WebListener;
import com.jsmartframework.web.annotation.WebSecurity;
import com.jsmartframework.web.annotation.WebServlet;
import com.jsmartframework.web.config.Constants;
import com.jsmartframework.web.config.UrlPattern;
import com.jsmartframework.web.listener.CsrfRequestListener;
import com.jsmartframework.web.listener.WebAsyncListener;
import com.jsmartframework.web.util.WebUtils;

import org.apache.commons.lang.StringUtils;
import org.reflections.Reflections;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Controller;

import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Scanner;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.inject.Inject;
import javax.naming.Binding;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingEnumeration;
import javax.servlet.Filter;
import javax.servlet.ServletContext;
import javax.servlet.ServletContextListener;
import javax.servlet.ServletException;
import javax.servlet.ServletRequestListener;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpSessionListener;

public enum BeanHandler {

    HANDLER();

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

    private static final Pattern INCLUDE_PATTERN = Pattern.compile("<%@*.include.*file=\"(.*)\".*%>");

    private static final Pattern HANDLER_EL_PATTERN = Pattern.compile(EL_PATTERN.pattern() + "|"
            + INCLUDE_PATTERN.pattern() + "|" + ID_PATTERN.pattern() + "|" + JSP_PATTERN.pattern());

    private static final Pattern SPRING_VALUE_PATTERN = Pattern.compile("[\\$,\\{,\\}]*");

    Map<String, Class<?>> webBeans = new ConcurrentHashMap<>();

    Map<String, Class<?>> authBeans = new ConcurrentHashMap<>();

    Map<String, Class<?>> asyncBeans = new ConcurrentHashMap<>();

    Map<String, Class<?>> webServlets = new ConcurrentHashMap<>();

    Map<String, Class<?>> webFilters = new ConcurrentHashMap<>();

    Map<String, Class<?>> requestPaths = new ConcurrentHashMap<>();

    Map<String, Class<?>> securityListeners = new ConcurrentHashMap<>();

    Set<ServletContextListener> contextListeners = new HashSet<>();

    Set<HttpSessionListener> sessionListeners = new HashSet<>();

    Set<ServletRequestListener> requestListeners = new HashSet<>();

    Map<String, AnnotatedFunction> beanMethodFunctions = new ConcurrentHashMap<>();

    private Map<String, List<AnnotatedFunction>> annotatedFunctions = new ConcurrentHashMap<>();

    private Map<String, AnnotatedAction> annotatedActions = new ConcurrentHashMap<>();

    private Map<String, String> forwardPaths = new ConcurrentHashMap<>();

    private Map<Class<?>, String> jndiMapping = new ConcurrentHashMap<>();

    private Map<String, JspPageBean> jspPageBeans = new ConcurrentHashMap<>();

    private InitialContext initialContext;

    private ApplicationContext springContext;

    void init(ServletContext context) {
        checkWebXmlPath(context);
        initJndiMapping();
        initAnnotatedBeans();
        initForwardPaths(context);
        initJspPageBeans(context);
    }

    void destroy(ServletContext context) {
        try {
            finalizeWebBeans(context);
            authBeans.clear();
            webBeans.clear();
            asyncBeans.clear();
            webServlets.clear();
            webFilters.clear();
            requestPaths.clear();
            securityListeners.clear();
            contextListeners.clear();
            sessionListeners.clear();
            requestListeners.clear();
            forwardPaths.clear();
            jspPageBeans.clear();
            annotatedFunctions.clear();
            beanMethodFunctions.clear();
            annotatedActions.clear();
            jndiMapping.clear();
            initialContext = null;
            springContext = null;
        } catch (Exception ex) {
            LOGGER.log(Level.SEVERE, "Failed to destroy BeanHandler: " + ex.getMessage());
        }
    }

    void setSpringContext(ApplicationContext springContext) {
        this.springContext = springContext;
    }

    @Deprecated
    boolean executePreSubmit(Object bean, String action) {
        for (Method method : HELPER.getPreSubmitMethods(bean.getClass())) {
            for (String onAction : method.getAnnotation(PreSubmit.class).onActions()) {
                try {
                    if (action.equalsIgnoreCase(onAction)) {
                        Boolean result = (Boolean) method.invoke(bean, null);
                        return result != null ? result : true;
                    }
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
        }
        return true;
    }

    boolean executePreAction(Object bean, String action) {
        for (Method method : HELPER.getPreActionMethods(bean.getClass())) {
            for (String onAction : method.getAnnotation(PreAction.class).onActions()) {
                try {
                    if (action.equalsIgnoreCase(onAction)) {
                        Boolean result = (Boolean) method.invoke(bean, null);
                        return result != null ? result : true;
                    }
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
        }
        return true;
    }

    @Deprecated
    void executePostSubmit(Object bean, String action) {
        for (Method method : HELPER.getPostSubmitMethods(bean.getClass())) {
            for (String onAction : method.getAnnotation(PostSubmit.class).onActions()) {
                try {
                    if (action.equalsIgnoreCase(onAction)) {
                        method.invoke(bean, null);
                        return;
                    }
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
            return;
        }
    }

    void executePostAction(Object bean, String action) {
        for (Method method : HELPER.getPostActionMethods(bean.getClass())) {
            for (String onAction : method.getAnnotation(PostAction.class).onActions()) {
                try {
                    if (action.equalsIgnoreCase(onAction)) {
                        method.invoke(bean, null);
                        return;
                    }
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
            return;
        }
    }

    void executePreDestroy(Object bean) {
        for (Method method : HELPER.getPreDestroyMethods(bean.getClass())) {
            if (method.isAnnotationPresent(PreDestroy.class)) {
                try {
                    method.invoke(bean, null);
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
                return;
            }
        }
    }

    void executePostConstruct(Object bean) {
        for (Method method : HELPER.getPostConstructMethods(bean.getClass())) {
            if (method.isAnnotationPresent(PostConstruct.class)) {
                try {
                    method.invoke(bean, null);
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
                return;
            }
        }
    }

    void executePreSet(String name, Object bean, Map<String, String> expressions) {
        if (expressions == null || expressions.isEmpty()) {
            return;
        }
        try {
            for (Field field : HELPER.getPreSetFields(bean.getClass())) {
                for (Entry<String, String> expr : expressions.entrySet()) {
                    Matcher elMatcher = EL_PATTERN.matcher(expr.getValue());
                    if (elMatcher.find() && elMatcher.group(1).contains(name + "." + field.getName())) {

                        Matcher tagMatcher = J_TAG_PATTERN.matcher(expr.getKey());
                        if (tagMatcher.find()) {
                            String jTag = tagMatcher.group(1);
                            String jParam = tagMatcher.group(2);

                            EXPRESSIONS.handleRequestExpression(jTag, expr.getValue(), jParam);
                            expressions.remove(expr.getKey());
                            break;
                        }
                    }
                }
            }
        } catch (Exception ex) {
            LOGGER.log(Level.SEVERE, "Execution of PreSet on WebBean [" + bean + "] failed: " + ex.getMessage());
        }
    }

    boolean containsUnescapeMethod(String[] names) {
        if (names != null && names.length > 1) {
            Class<?> clazz = webBeans.get(names[0]);
            if (clazz == null) {
                return false;
            }
            for (String methodName : HELPER.getUnescapeMethods(clazz)) {
                if (names[1].equalsIgnoreCase(methodName)) {
                    return true;
                }
            }
        }
        return false;
    }

    Map<String, String> getRequestExpressions(HttpServletRequest request) {
        return EXPRESSIONS.getRequestExpressions(request);
    }

    String handleRequestExpressions(Map<String, String> expressions) throws ServletException, IOException {
        String submitParam = null;
        String submitExpr = null;

        for (Entry<String, String> expr : expressions.entrySet()) {
            Matcher matcher = J_TAG_PATTERN.matcher(expr.getKey());
            if (matcher.find()) {

                String jTag = matcher.group(1);
                String jParam = matcher.group(2);
                EXPRESSIONS.handleRequestExpression(jTag, expr.getValue(), jParam);

                if (jTag.equals(TagHandler.J_SBMT)) {
                    submitExpr = expr.getValue();
                    submitParam = jParam;
                }
            }
        }
        if (submitExpr != null) {
            try {
                return EXPRESSIONS.handleSubmitExpression(submitExpr, submitParam);
            } catch (Exception ex) {
                LOGGER.log(Level.SEVERE, "Error executing expression [" + submitExpr + "] with parameters ["
                        + submitParam + "]: " + ex.getMessage());
                throw ex;
            }
        }
        return null;
    }

    void instantiateBeans(String path, Map<String, String> expressions) throws Exception {
        JspPageBean jspPageBean = jspPageBeans.get(path);
        if (jspPageBean != null) {
            for (String name : jspPageBean.getBeanNames()) {
                instantiateBean(name, expressions);
            }
        }
    }

    private Object instantiateBean(String name, Map<String, String> expressions) throws Exception {
        Object bean = null;
        ServletContext context = WebContext.getApplication();
        HttpSession session = WebContext.getSession();
        HttpServletRequest request = WebContext.getRequest();

        if (request.getAttribute(name) != null) {
            bean = request.getAttribute(name);
            executeInjection(bean);
            return bean;
        }

        synchronized (session) {
            if (session.getAttribute(name) != null) {
                bean = session.getAttribute(name);
                executeInjection(bean);
                return bean;
            }
        }

        if (context.getAttribute(name) != null) {
            bean = context.getAttribute(name);
            executeInjection(bean);
            return bean;
        }

        if (webBeans.containsKey(name)) {
            Class<?> clazz = webBeans.get(name);
            bean = clazz.newInstance();

            WebBean webBean = clazz.getAnnotation(WebBean.class);
            if (webBean.scope().equals(ScopeType.REQUEST)) {
                request.setAttribute(name, bean);

            } else if (webBean.scope().equals(ScopeType.SESSION)) {
                synchronized (session) {
                    session.setAttribute(name, bean);
                }
            } else if (webBean.scope().equals(ScopeType.APPLICATION)) {
                context.setAttribute(name, bean);

            } else {
                return null;
            }

            executeInjection(bean);
            executePreSet(name, bean, expressions);
            executePostConstruct(bean);
        }
        return bean;
    }

    Object instantiateAsyncBean(String path) throws Exception {
        Class<?> clazz = asyncBeans.get(path);
        if (clazz != null) {
            Object bean = clazz.newInstance();
            executeInjection(bean);
            return bean;
        }
        return null;
    }

    void executeInjection(Object object) {
        try {
            HttpSession session = WebContext.getSession();
            HttpServletRequest request = WebContext.getRequest();

            for (Field field : HELPER.getBeanFields(object.getClass())) {
                if (field.getAnnotations().length == 0) {
                    continue;
                }

                if (field.isAnnotationPresent(Inject.class)) {
                    WebBean webBean = field.getType().getAnnotation(WebBean.class);
                    if (webBean != null) {
                        field.setAccessible(true);
                        field.set(object, instantiateBean(HELPER.getClassName(webBean, field.getType()), null));
                        continue;
                    }

                    AuthBean authBean = field.getType().getAnnotation(AuthBean.class);
                    if (authBean != null) {
                        field.setAccessible(true);
                        if (authBean.type() == AuthType.SESSION) {
                            field.set(object,
                                    instantiateAuthBean(HELPER.getClassName(authBean, field.getType()), session));

                        } else if (authBean.type() == AuthType.REQUEST) {
                            field.set(object,
                                    instantiateAuthBean(HELPER.getClassName(authBean, field.getType()), request));
                        }
                        continue;
                    }
                }

                // Inject URL Parameters
                if (field.isAnnotationPresent(QueryParam.class)) {
                    QueryParam queryParam = field.getAnnotation(QueryParam.class);
                    String paramValue = request.getParameter(queryParam.value());

                    if (paramValue != null) {
                        field.setAccessible(true);
                        field.set(object, ExpressionHandler.EXPRESSIONS.decodeUrl(paramValue));
                    }
                    continue;
                }

                // Inject VarMapping case present
                if (field.isAnnotationPresent(ExposeVar.class)) {
                    Map<?, ?> varMapping = HELPER.getExposeVarMapping(field);
                    if (varMapping != null) {
                        field.setAccessible(true);
                        field.set(object, varMapping);
                    }
                }

                // Inject dependencies
                if (initialContext != null && jndiMapping.containsKey(field.getType())) {
                    field.setAccessible(true);
                    field.set(object, initialContext.lookup(jndiMapping.get(field.getType())));
                    continue;
                }

                if (springContext != null) {
                    String beanName = HELPER.getClassName(field.getType().getSimpleName());
                    if (springContext.containsBean(beanName) || field.isAnnotationPresent(Qualifier.class)) {
                        field.setAccessible(true);
                        field.set(object, springContext.getBean(field.getType()));

                    } else if (field.isAnnotationPresent(Value.class)) {
                        String propertyName = field.getAnnotation(Value.class).value();
                        propertyName = SPRING_VALUE_PATTERN.matcher(propertyName).replaceAll("");
                        field.setAccessible(true);
                        field.set(object,
                                springContext.getEnvironment().getProperty(propertyName, field.getType()));
                    }
                }
            }
        } catch (Exception ex) {
            LOGGER.log(Level.SEVERE, "Injection on object [" + object + "] failed", ex);
        }
    }

    void finalizeAsyncBean(Object bean, HttpServletRequest request) {
        if (bean != null) {
            finalizeInjection(bean, request);
        }
    }

    void finalizeWebBeans(ServletContext servletContext) {
        List<String> names = Collections.list(servletContext.getAttributeNames());
        for (String name : names) {
            Object bean = servletContext.getAttribute(name);
            if (bean == null) {
                continue;
            }
            if (bean.getClass().isAnnotationPresent(WebBean.class)) {
                finalizeWebBean(bean, servletContext);
            }
        }
    }

    private void finalizeWebBean(Object bean, ServletContext servletContext) {
        if (bean != null) {
            executePreDestroy(bean);
            finalizeInjection(bean, servletContext);
            WebBean webBean = bean.getClass().getAnnotation(WebBean.class);
            servletContext.removeAttribute(HELPER.getClassName(webBean, bean.getClass()));
        }
    }

    void finalizeBeans(HttpSession session) {
        synchronized (session) {
            List<String> names = Collections.list(session.getAttributeNames());

            for (String name : names) {
                Object bean = session.getAttribute(name);
                if (bean == null) {
                    continue;
                }
                if (bean.getClass().isAnnotationPresent(WebBean.class)) {
                    finalizeWebBean(bean, session);

                } else if (bean.getClass().isAnnotationPresent(AuthBean.class)) {
                    finalizeAuthBean(bean, session);
                }
            }
        }
    }

    private void finalizeWebBean(Object bean, HttpSession session) {
        if (bean != null) {
            executePreDestroy(bean);
            finalizeInjection(bean, session);
            WebBean webBean = bean.getClass().getAnnotation(WebBean.class);
            session.removeAttribute(HELPER.getClassName(webBean, bean.getClass()));
        }
    }

    public void finalizeBeans(HttpServletRequest request, HttpServletResponse response) {
        List<String> names = Collections.list(request.getAttributeNames());
        for (String name : names) {
            Object bean = request.getAttribute(name);
            if (bean == null) {
                continue;
            }

            Field[] exposeVars = HELPER.getExposeVarFields(bean.getClass());
            for (int i = 0; i < exposeVars.length; i++) {
                try {
                    Object value = exposeVars[i].get(bean);
                    if (value != null) {
                        setExposeVarAttribute(request, exposeVars[i].getName(), value);
                    }
                } catch (Exception ex) {
                    LOGGER.log(Level.SEVERE, "Could not expose var [" + exposeVars[i] + "]", ex);
                }
            }

            if (bean.getClass().isAnnotationPresent(WebBean.class)) {
                finalizeWebBean(bean, request);

            } else if (bean.getClass().isAnnotationPresent(AuthBean.class)) {
                finalizeAuthBean(bean, request, response);

            } else if (bean.getClass().isAnnotationPresent(WebSecurity.class)) {
                finalizeWebSecurity(bean, request);
            }
        }
    }

    private void setExposeVarAttribute(HttpServletRequest request, String name, Object value) {
        Map<String, Object> exposeVars = (Map) request.getAttribute(REQUEST_EXPOSE_VARS_ATTR);
        if (exposeVars == null) {
            request.setAttribute(REQUEST_EXPOSE_VARS_ATTR, (exposeVars = new ConcurrentHashMap<>()));
        }
        exposeVars.put(name, value);
    }

    private void finalizeWebBean(Object bean, HttpServletRequest request) {
        if (bean != null) {
            executePreDestroy(bean);
            finalizeInjection(bean, request);
            WebBean webBean = bean.getClass().getAnnotation(WebBean.class);
            request.removeAttribute(HELPER.getClassName(webBean, bean.getClass()));
        }
    }

    private void finalizeInjection(Object bean, Object servletObject) {
        try {
            for (Field field : HELPER.getBeanFields(bean.getClass())) {
                if (field.isAnnotationPresent(Inject.class)) {

                    if (field.getType().isAnnotationPresent(WebBean.class)) {
                        field.setAccessible(true);

                        if (servletObject instanceof HttpServletRequest) {
                            finalizeWebBean(field.get(bean), (HttpServletRequest) servletObject);

                        } else if (servletObject instanceof HttpSession) {
                            finalizeWebBean(field.get(bean), (HttpSession) servletObject);

                        } else if (servletObject instanceof ServletContext) {
                            finalizeWebBean(field.get(bean), (ServletContext) servletObject);
                        }
                        field.set(bean, null);
                        continue;
                    }

                    if (field.getType().isAnnotationPresent(AuthBean.class)) {
                        field.setAccessible(true);
                        field.set(bean, null);
                        continue;
                    }
                }
            }
        } catch (Exception ex) {
            LOGGER.log(Level.SEVERE, "Finalize injection on WebBean [" + bean + "] failed: " + ex.getMessage());
        }
    }

    void instantiateAuthBean(HttpSession session) {
        for (String name : authBeans.keySet()) {
            AuthBean authBean = authBeans.get(name).getAnnotation(AuthBean.class);

            if (authBean.type() == AuthType.SESSION) {
                instantiateAuthBean(name, session);
            }
            // We must have only one @AuthBean mapped
            break;
        }
    }

    private Object instantiateAuthBean(String name, HttpSession session) {
        synchronized (session) {
            Object bean = session.getAttribute(name);
            if (bean != null) {
                return bean;
            }
            bean = initializeAuthBean(name, null);
            session.setAttribute(name, bean);
            return bean;
        }
    }

    void instantiateAuthBean(HttpServletRequest request) {
        for (String name : authBeans.keySet()) {
            AuthBean authBean = authBeans.get(name).getAnnotation(AuthBean.class);

            if (authBean.type() == AuthType.REQUEST) {
                instantiateAuthBean(name, request);
            }
            // We must have only one @AuthBean mapped
            break;
        }
    }

    private Object instantiateAuthBean(String name, HttpServletRequest request) {
        Object bean = request.getAttribute(name);
        if (bean != null) {
            return bean;
        }
        bean = initializeAuthBean(name, request);
        request.setAttribute(name, bean);
        return bean;
    }

    private Object initializeAuthBean(String name, HttpServletRequest request) {
        Object bean = null;
        try {
            bean = authBeans.get(name).newInstance();
            AuthBean authBean = authBeans.get(name).getAnnotation(AuthBean.class);

            for (Field field : HELPER.getBeanFields(bean.getClass())) {
                if (field.getAnnotations().length == 0) {
                    continue;
                }

                // Set authentication cookies when initializing @AuthBean
                if (request != null && field.isAnnotationPresent(AuthField.class)) {
                    AuthField authField = field.getAnnotation(AuthField.class);
                    field.setAccessible(true);
                    String fieldValue = WebUtils.getCookie(request, authField.value());

                    if (fieldValue != null) {
                        fieldValue = AuthEncrypter.decrypt(request, authBean.secretKey(), fieldValue);
                    }
                    field.set(bean, fieldValue);
                    continue;
                }

                // Inject VarMapping case present
                if (field.isAnnotationPresent(ExposeVar.class)) {
                    Map<?, ?> varMapping = HELPER.getExposeVarMapping(field);
                    if (varMapping != null) {
                        field.setAccessible(true);
                        field.set(bean, varMapping);
                    }
                }

                if (initialContext != null && jndiMapping.containsKey(field.getType())) {
                    field.setAccessible(true);
                    field.set(bean, initialContext.lookup(jndiMapping.get(field.getType())));
                    continue;
                }

                if (springContext != null) {
                    String beanName = HELPER.getClassName(field.getType().getSimpleName());
                    if (springContext.containsBean(beanName) || field.isAnnotationPresent(Qualifier.class)) {
                        field.setAccessible(true);
                        field.set(bean, springContext.getBean(field.getType()));

                    } else if (field.isAnnotationPresent(Value.class)) {
                        String propertyName = field.getAnnotation(Value.class).value();
                        propertyName = SPRING_VALUE_PATTERN.matcher(propertyName).replaceAll("");
                        field.setAccessible(true);
                        field.set(bean, springContext.getEnvironment().getProperty(propertyName, field.getType()));
                    }
                }
            }
            executePostConstruct(bean);

        } catch (Exception ex) {
            LOGGER.log(Level.SEVERE, "Injection on AuthBean [" + bean + "] failed: " + ex.getMessage());
        }
        return bean;
    }

    private void finalizeAuthBean(Object bean, HttpSession session) {
        executePreDestroy(bean);
        AuthBean authBean = bean.getClass().getAnnotation(AuthBean.class);
        session.removeAttribute(HELPER.getClassName(authBean, bean.getClass()));
    }

    private void finalizeAuthBean(Object bean, HttpServletRequest request, HttpServletResponse response) {
        executePreDestroy(bean);
        AuthBean authBean = bean.getClass().getAnnotation(AuthBean.class);
        try {
            for (Field field : HELPER.getBeanFields(bean.getClass())) {
                if (field.getAnnotations().length > 0) {
                    field.setAccessible(true);

                    if (field.isAnnotationPresent(AuthField.class)) {
                        AuthField authField = field.getAnnotation(AuthField.class);

                        Object value = field.get(bean);
                        if (value != null) {
                            // Return encrypted auth fields as cookies to check if customer is still
                            // logged on next request
                            String cookieValue = AuthEncrypter.encrypt(request, authBean.secretKey(), value);

                            Cookie cookie = getAuthenticationCookie(request, authField.value(), cookieValue, -1);
                            response.addCookie(cookie);
                        } else {
                            // Case value is null we force Cookie deletion on client side
                            Cookie cookie = getAuthenticationCookie(request, authField.value(), null, 0);
                            response.addCookie(cookie);
                        }
                    }
                    field.set(bean, null);
                }
            }
        } catch (Exception ex) {
            LOGGER.log(Level.SEVERE, "Finalize injection on AuthBean [" + bean + "] failed: " + ex.getMessage());
        }
        request.removeAttribute(HELPER.getClassName(authBean, bean.getClass()));
    }

    private Cookie getAuthenticationCookie(HttpServletRequest request, String name, String value, int age) {
        Cookie cookie = new Cookie(name, value);
        cookie.setHttpOnly(true);
        cookie.setPath("/");
        cookie.setMaxAge(age);
        return cookie;
    }

    void instantiateWebSecurity(HttpServletRequest request) {
        for (String name : securityListeners.keySet()) {
            WebSecurity webSecurity = securityListeners.get(name).getAnnotation(WebSecurity.class);

            Object listener = request.getAttribute(name);
            if (listener == null) {
                try {
                    listener = securityListeners.get(name).newInstance();
                    executeInjection(listener);
                    executePostConstruct(listener);
                    request.setAttribute(name, listener);
                } catch (Exception ex) {
                    LOGGER.log(Level.SEVERE, "Injection on WebSecurity [" + name + "] failed: " + ex.getMessage());
                }
            }
            // We must have only one @WebSecurity mapped
            break;
        }
    }

    private void finalizeWebSecurity(Object listener, HttpServletRequest request) {
        executePreDestroy(listener);
        WebSecurity webSecurity = listener.getClass().getAnnotation(WebSecurity.class);
        request.removeAttribute(HELPER.getClassName(webSecurity, listener.getClass()));
    }

    AuthPath checkAuthentication(String path) throws ServletException {
        if (authBeans.isEmpty() && !CONFIG.getContent().getSecureUrls().isEmpty()) {
            throw new ServletException("Not found AuthBean mapped in your system. Once your system "
                    + "has secure urls, please map a bean with @AuthBean!");
        }

        AuthBean authBean = null;
        boolean authenticated = true;

        for (String name : authBeans.keySet()) {
            authBean = authBeans.get(name).getAnnotation(AuthBean.class);

            if (authBean.type() == AuthType.SESSION) {
                HttpSession session = WebContext.getSession();
                synchronized (session) {
                    authenticated = checkAuthentication(session.getAttribute(name));
                }
            } else if (authBean.type() == AuthType.REQUEST) {
                HttpServletRequest request = WebContext.getRequest();
                authenticated = checkAuthentication(request.getAttribute(name));
            }
            // We must have only one @AuthBean mapped
            break;
        }

        // Access secure url
        //  - User authenticated ===>>> ok redirect to path
        //  - User not authenticated ===>>> redirect to login
        if (CONFIG.getContent().containsSecureUrl(path)) {
            if (authenticated) {
                return new AuthPath(path);
            } else {
                return new AuthPath(WebUtils.decodePath(authBean.loginPath()));
            }
        }
        // Access non secure url
        //   - User authenticated
        //         - access login page or except page ===>>> redirect to home
        //         - other cases ===>>> ok redirect to path
        //   - User not authenticated ===>>> ok redirect to path
        else {
            if (authenticated) {
                if (authBean != null && (path.equals(WebUtils.decodePath(authBean.loginPath()))
                        || CONFIG.getContent().containsNonSecureUrlOnly(path))) {
                    return new AuthPath(WebUtils.decodePath(authBean.homePath()), true);
                } else {
                    return new AuthPath(path);
                }
            } else {
                return new AuthPath(path);
            }
        }
    }

    private boolean checkAuthentication(Object bean) throws ServletException {
        boolean authenticated = true;
        if (bean == null) {
            return authenticated;
        }

        // Look for fields in order to check if the request is already authenticated
        for (Field field : HELPER.getAuthFields(bean.getClass())) {
            try {
                field.setAccessible(true);
                // If field is not set it means that user is not authenticated
                if (field.get(bean) == null) {
                    authenticated = false;
                    break;
                }
            } catch (Exception ex) {
                throw new ServletException("AuthField not accessible: " + ex.getMessage(), ex);
            }
        }

        // Check authenticate methods to validate credentials
        for (Method method : HELPER.getAuthMethods(bean.getClass())) {
            try {
                Boolean auth = (Boolean) method.invoke(bean);
                authenticated &= auth != null ? auth : false;
            } catch (Exception ex) {
                throw new ServletException("AuthMethod not accessible: " + ex.getMessage(), ex);
            }
        }
        return authenticated;
    }

    @SuppressWarnings("all")
    Integer checkAuthorization(String path) {
        if (CONFIG.getContent().containsSecureUrl(path) && !authBeans.isEmpty()) {
            AuthBean authBean = null;
            Collection<String> userAccess = getUserAuthorizationAccess();

            // Check mapped urls
            UrlPattern urlPattern = CONFIG.getContent().getUrlPattern(path);
            if (urlPattern != null && urlPattern.getAccess() != null) {

                for (String access : urlPattern.getAccess()) {
                    if (userAccess.contains(access) || access.equals("*")) {
                        return null; // It means, authorized user
                    }
                }
                return HttpServletResponse.SC_FORBIDDEN;
            }
        }
        return null; // It means, authorized user
    }

    Collection<String> getUserAuthorizationAccess() {
        HttpServletRequest request = WebContext.getRequest();

        for (String name : authBeans.keySet()) {
            AuthBean authBean = authBeans.get(name).getAnnotation(AuthBean.class);

            if (authBean.type() == AuthType.SESSION) {
                HttpSession session = WebContext.getSession();
                synchronized (session) {
                    return getUserAuthorizationAccess(session.getAttribute(name), request);
                }
            } else if (authBean.type() == AuthType.REQUEST) {
                return getUserAuthorizationAccess(request.getAttribute(name), request);
            }
            // We must have only one @AuthBean mapped
            break;
        }
        return Collections.emptyList();
    }

    @SuppressWarnings("unchecked")
    Collection<String> getUserAuthorizationAccess(Object bean, HttpServletRequest request) {
        if (request.getAttribute(Constants.REQUEST_USER_ACCESS) == null) {
            Collection<String> userAccess = new HashSet<>();

            for (Field field : HELPER.getAuthAccess(bean.getClass())) {
                try {
                    field.setAccessible(true);
                    Object object = field.get(bean);
                    if (object != null) {
                        userAccess.addAll((Collection<String>) object);
                    }
                } catch (Exception ex) {
                    LOGGER.log(Level.SEVERE, "AuthAccess mapped on WebBean [" + bean + "] could "
                            + "not be cast to Collection<String>: " + ex.getMessage());
                }
                break;
            }
            request.setAttribute(Constants.REQUEST_USER_ACCESS, userAccess);
        }
        return (Collection<String>) request.getAttribute(Constants.REQUEST_USER_ACCESS);
    }

    boolean checkExecuteAuthorization(Object bean, String expression, HttpServletRequest request) {
        for (Method method : HELPER.getExecuteAccessMethods(bean.getClass())) {
            ExecuteAccess execAccess = method.getAnnotation(ExecuteAccess.class);

            if (execAccess.access().length > 0 && expression.contains(method.getName())) {
                Collection<String> userAccess = getUserAuthorizationAccess(bean, request);

                if (!userAccess.isEmpty()) {
                    for (String access : execAccess.access()) {
                        if (userAccess.contains(access)) {
                            return true;
                        }
                    }
                    return false;
                }
                break;
            }
        }
        return true;
    }

    Integer checkWebSecurityToken(HttpServletRequest request) {
        if (securityListeners.isEmpty()) {
            return null; // It means, valid user access
        }

        for (String name : securityListeners.keySet()) {
            WebSecurity webSecurity = securityListeners.get(name).getAnnotation(WebSecurity.class);
            CsrfRequestListener listener = (CsrfRequestListener) request.getAttribute(name);

            String csrfName = request.getHeader(Constants.CSRF_TOKEN_NAME);
            if (StringUtils.isBlank(csrfName)) {
                csrfName = request.getParameter(Constants.CSRF_TOKEN_NAME);
            }
            String csrfToken = request.getHeader(Constants.CSRF_TOKEN_VALUE);
            if (StringUtils.isBlank(csrfToken)) {
                csrfToken = request.getParameter(Constants.CSRF_TOKEN_VALUE);
            }

            if (StringUtils.isBlank(csrfName) && StringUtils.isBlank(csrfToken)) {
                return HttpServletResponse.SC_FORBIDDEN;
            }

            if (!webSecurity.disableEncrypt()) {
                csrfName = CsrfEncrypter.decrypt(request, webSecurity.secretKey(), csrfName);
                csrfToken = CsrfEncrypter.decrypt(request, webSecurity.secretKey(), csrfToken);
            }
            if (!listener.isValidToken(new CsrfAdapter(csrfName, csrfToken))) {
                return HttpServletResponse.SC_FORBIDDEN;
            }
            // We must have only one @webSecurity mapped
            break;
        }
        return null; // It means, valid user access
    }

    void generateWebSecurityToken(HttpServletRequest request, HttpServletResponse response) {
        if (request.getAttribute(Constants.REQUEST_META_DATA_CSRF_TOKEN_NAME) == null) {
            for (String name : securityListeners.keySet()) {

                WebSecurity webSecurity = securityListeners.get(name).getAnnotation(WebSecurity.class);
                CsrfRequestListener listener = (CsrfRequestListener) request.getAttribute(name);

                CsrfAdapter csrfAdapter = listener.generateToken();
                if (csrfAdapter == null || StringUtils.isBlank(csrfAdapter.getName())
                        || StringUtils.isBlank(csrfAdapter.getToken())) {
                    LOGGER.warning("Class [" + name + "] returned invalid token from generateToken method");
                    return;
                }

                String csrfName = csrfAdapter.getName();
                String csrfToken = csrfAdapter.getToken();

                if (!webSecurity.disableEncrypt()) {
                    csrfName = CsrfEncrypter.encrypt(request, webSecurity.secretKey(), csrfAdapter.getName());
                    csrfToken = CsrfEncrypter.encrypt(request, webSecurity.secretKey(), csrfAdapter.getToken());
                }
                request.setAttribute(Constants.REQUEST_META_DATA_CSRF_TOKEN_NAME, csrfName);
                request.setAttribute(Constants.REQUEST_META_DATA_CSRF_TOKEN_VALUE, csrfToken);
                // We must have only one @webSecurity mapped
                break;
            }
        }
    }

    private void initAnnotatedBeans() {
        if (CONFIG.getContent().getPackageScan() == null) {
            LOGGER.log(Level.SEVERE, "None [package-scan] tag was found on [" + Constants.WEB_CONFIG_XML
                    + "] file! Skipping package scanning.");
            return;
        }

        Object[] packages = CONFIG.getContent().getPackageScan().split(",");
        Reflections reflections = new Reflections(packages);

        initAnnotatedWebBeans(reflections);
        initAnnotatedAuthBeans(reflections);
        initAnnotatedRequestPaths(reflections);
        initAnnotatedAsyncBeans(reflections);
        initAnnotatedWebServlets(reflections);
        initAnnotatedWebFilters(reflections);
        initAnnotatedWebListeners(reflections);
        initAnnotatedWebSecurities(reflections);
    }

    private void initAnnotatedWebBeans(Reflections reflections) {
        Set<Class<?>> annotations = reflections.getTypesAnnotatedWith(WebBean.class);

        for (Class<?> clazz : annotations) {
            WebBean bean = clazz.getAnnotation(WebBean.class);
            LOGGER.log(Level.INFO, "Mapping WebBean class: " + clazz);

            if (bean.scope() == ScopeType.SESSION && !Serializable.class.isAssignableFrom(clazz)) {
                throw new RuntimeException("Mapped WebBean class [" + clazz + "] with scope [" + bean.scope() + "] "
                        + "must implement java.io.Serializable interface");
            }

            HELPER.setBeanFields(clazz);
            HELPER.setBeanMethods(clazz);
            String className = HELPER.getClassName(bean, clazz);
            webBeans.put(className, clazz);

            initAnnotatedActionMethods(className, clazz);
        }

        if (webBeans.isEmpty()) {
            LOGGER.log(Level.INFO, "WebBeans were not mapped!");
        }
    }

    private void initAnnotatedActionMethods(String className, Class<?> clazz) {
        for (Method method : HELPER.getBeanMethods(clazz)) {
            List<Arg> arguments = new ArrayList<>();

            for (Annotation[] annotations : method.getParameterAnnotations()) {
                for (Annotation annotation : annotations) {
                    if (annotation instanceof Arg) {
                        arguments.add((Arg) annotation);
                    }
                }
            }

            if (method.isAnnotationPresent(Function.class)) {
                AnnotatedFunction annotatedFunction = new AnnotatedFunction(method, className, arguments);

                // Keep track of functions per bean method
                beanMethodFunctions.put(String.format(BEAN_METHOD_NAME_FORMAT, className, method.getName()),
                        annotatedFunction);

                List<String> urlPaths = HELPER.cleanPaths(annotatedFunction.getFunction().forPaths());

                for (String urlPath : urlPaths) {
                    List<AnnotatedFunction> pathFunctions = annotatedFunctions.get(urlPath);
                    if (pathFunctions == null) {
                        pathFunctions = new ArrayList<>();
                        annotatedFunctions.put(urlPath, pathFunctions);
                    }
                    pathFunctions.add(annotatedFunction);
                }
            }

            if (method.isAnnotationPresent(Action.class)) {
                AnnotatedAction annotatedAction = new AnnotatedAction(method, className, arguments);
                for (String id : annotatedAction.getAction().forIds()) {
                    annotatedActions.put(id, annotatedAction);
                }
            }
        }
    }

    private void initAnnotatedAuthBeans(Reflections reflections) {
        Set<Class<?>> annotations = reflections.getTypesAnnotatedWith(AuthBean.class);

        for (Class<?> clazz : annotations) {
            AuthBean authBean = clazz.getAnnotation(AuthBean.class);
            if (authBeans.isEmpty()) {
                LOGGER.log(Level.INFO, "Mapping AuthBean class: " + clazz);

                if (authBean.type() == AuthType.SESSION && !Serializable.class.isAssignableFrom(clazz)) {
                    throw new RuntimeException("Mapped AuthBean class [" + clazz + "] of type AuthType.SESSION "
                            + "must implement java.io.Serializable interface");
                }
                if (authBean.type() == AuthType.REQUEST
                        && authBean.secretKey().length() != AuthEncrypter.CYPHER_KEY_LENGTH) {
                    throw new RuntimeException("Mapped AuthBean annotation for class [" + clazz + "] must "
                            + "have its secretKey value with [" + AuthEncrypter.CYPHER_KEY_LENGTH + "] characters");
                }

                HELPER.setBeanFields(clazz);
                HELPER.setBeanMethods(clazz);
                HELPER.setAuthFields(clazz);
                HELPER.setAuthAccess(clazz);
                HELPER.setAuthMethods(clazz);

                if (HELPER.getAuthFields(clazz).length == 0) {
                    throw new RuntimeException("Mapped AuthBean class [" + clazz + "] must contain at "
                            + "least one field annotated with @AuthField");
                }
                if (HELPER.hasPrimitiveAuthFields(clazz)) {
                    throw new RuntimeException("Mapped AuthBean class [" + clazz + "] must have all fields "
                            + "annotated with @AuthField as non primitive type");
                }
                if (HELPER.getAuthMethods(clazz).length == 0) {
                    throw new RuntimeException("Mapped AuthBean class [" + clazz + "] must contain at "
                            + "least one method that is annotated with @AuthMethod "
                            + "and returns boolean value to return authentication status");
                }

                String className = HELPER.getClassName(authBean, clazz);
                authBeans.put(className, clazz);

                initAnnotatedActionMethods(className, clazz);
            } else {
                LOGGER.log(Level.SEVERE, "Only one AuthBean can be declared! Skipping class " + clazz);
            }
        }

        if (authBeans.isEmpty()) {
            LOGGER.log(Level.INFO, "AuthBean was not mapped!");
        }
        checkWebBeanConstraint(annotations, "@AuthBean");
    }

    private void initAnnotatedRequestPaths(Reflections reflections) {
        Set<Class<?>> annotations = reflections.getTypesAnnotatedWith(RequestPath.class);

        for (Class<?> clazz : annotations) {
            RequestPath requestPath = clazz.getAnnotation(RequestPath.class);
            LOGGER.log(Level.INFO, "Mapping RequestPath class: " + clazz);

            if (!clazz.isAnnotationPresent(Controller.class)) {
                throw new RuntimeException("Mapped RequestPath class [" + clazz + "] must be annotated with "
                        + "org.springframework.stereotype.Controller from Spring");
            }
            if (!requestPath.value().endsWith("*")) {
                throw new RuntimeException("Mapped class [" + clazz + "] annotated with @RequestPath must have its "
                        + "path annotation attribute ending with * character");
            }

            HELPER.setBeanFields(clazz);
            HELPER.setBeanMethods(clazz);
            requestPaths.put(requestPath.value(), clazz);
        }

        if (requestPaths.isEmpty()) {
            LOGGER.log(Level.INFO, "RequestPaths were not mapped!");
        }

        for (Class<?> pathClazz : requestPaths.values()) {
            for (Class<?> webClazz : webBeans.values()) {
                if (webClazz == pathClazz) {
                    LOGGER.log(Level.SEVERE,
                            "@WebBean class [" + webClazz + "] cannot be annotated with @RequestPath");
                }
            }
        }
        checkWebBeanConstraint(annotations, "@RequestPath");
        checkAuthBeanConstraint(annotations, "@RequestPath");
    }

    private void initAnnotatedAsyncBeans(Reflections reflections) {
        Set<Class<?>> annotations = reflections.getTypesAnnotatedWith(AsyncBean.class);

        for (Class<?> clazz : annotations) {
            AsyncBean asyncBean = clazz.getAnnotation(AsyncBean.class);
            LOGGER.log(Level.INFO, "Mapping AsyncBean class: " + clazz);

            if (!WebAsyncListener.class.isAssignableFrom(clazz)) {
                throw new RuntimeException(
                        "Mapped AsyncBean class [" + clazz + "] must implement " + "WebAsyncListener interface");
            }

            HELPER.setBeanFields(clazz);
            HELPER.setBeanMethods(clazz);
            String path = HELPER.getCleanPath(asyncBean.value());
            path = HELPER.matchUrlPattern(path);
            asyncBeans.put(path, clazz);
        }

        if (asyncBeans.isEmpty()) {
            LOGGER.log(Level.INFO, "AsyncBeans were not mapped!");
        }
        checkWebBeanConstraint(annotations, "@AsyncBean");
        checkAuthBeanConstraint(annotations, "@AsyncBean");
        checkRequestPathConstraint(annotations, "@AsyncBean");
    }

    private void initAnnotatedWebServlets(Reflections reflections) {
        Set<Class<?>> annotations = reflections.getTypesAnnotatedWith(WebServlet.class);

        for (Class<?> clazz : annotations) {
            WebServlet servlet = clazz.getAnnotation(WebServlet.class);
            LOGGER.log(Level.INFO, "Mapping WebServlet class: " + clazz);

            if (!HttpServlet.class.isAssignableFrom(clazz)) {
                throw new RuntimeException("Mapped WebServlet class " + "[" + clazz
                        + "] must extends javax.servlet.http.HttpServlet class");
            }

            HELPER.setBeanFields(clazz);
            HELPER.setBeanMethods(clazz);
            webServlets.put(HELPER.getClassName(servlet, clazz), clazz);
        }

        if (webServlets.isEmpty()) {
            LOGGER.log(Level.INFO, "WebServlets were not mapped!");
        }
        checkWebBeanConstraint(annotations, "@WebServlet");
        checkAuthBeanConstraint(annotations, "@WebServlet");
        checkRequestPathConstraint(annotations, "@WebServlet");
        checkAsyncBeanConstraint(annotations, "@WebServlet");
    }

    private void initAnnotatedWebFilters(Reflections reflections) {
        Set<Class<?>> annotations = reflections.getTypesAnnotatedWith(WebFilter.class);

        for (Class<?> clazz : annotations) {
            WebFilter filter = clazz.getAnnotation(WebFilter.class);
            LOGGER.log(Level.INFO, "Mapping WebFilter class: " + clazz);

            if (!Filter.class.isAssignableFrom(clazz)) {
                throw new RuntimeException("Mapped WebFilter class [" + clazz + "] "
                        + "must implement javax.servlet.Filter interface");
            }

            HELPER.setBeanFields(clazz);
            HELPER.setBeanMethods(clazz);
            webFilters.put(HELPER.getClassName(filter, clazz), clazz);
        }

        if (webFilters.isEmpty()) {
            LOGGER.log(Level.INFO, "WebFilters were not mapped!");
        }
        checkWebBeanConstraint(annotations, "@WebFilter");
        checkAuthBeanConstraint(annotations, "@WebFilter");
        checkRequestPathConstraint(annotations, "@WebFilter");
        checkAsyncBeanConstraint(annotations, "@WebFilter");
    }

    private void initAnnotatedWebListeners(Reflections reflections) {
        Set<Class<?>> annotations = reflections.getTypesAnnotatedWith(WebListener.class);

        for (Class<?> clazz : annotations) {
            try {
                Object listenerObj = clazz.newInstance();
                if (ServletContextListener.class.isInstance(listenerObj)) {
                    LOGGER.log(Level.INFO, "Mapping ServletContextListener class [" + clazz + "]");
                    HELPER.setBeanFields(clazz);
                    HELPER.setBeanMethods(clazz);
                    contextListeners.add((ServletContextListener) listenerObj);

                } else if (HttpSessionListener.class.isInstance(listenerObj)) {
                    LOGGER.log(Level.INFO, "Mapping HttpSessionListener class [" + clazz + "]");
                    HELPER.setBeanFields(clazz);
                    HELPER.setBeanMethods(clazz);
                    sessionListeners.add((HttpSessionListener) listenerObj);

                } else if (ServletRequestListener.class.isInstance(listenerObj)) {
                    LOGGER.log(Level.INFO, "Mapping ServletRequestListener class [" + clazz + "]");
                    HELPER.setBeanFields(clazz);
                    HELPER.setBeanMethods(clazz);
                    requestListeners.add((ServletRequestListener) listenerObj);

                } else {
                    throw new RuntimeException("Mapped WebListener class [" + clazz + "] "
                            + "must implement javax.servlet.ServletContextListener, "
                            + "javax.servlet.http.HttpSessionListener or javax.servlet.ServletRequestListener interface");
                }
            } catch (Exception ex) {
                LOGGER.log(Level.SEVERE,
                        "WebListener class [" + clazz.getName() + "] " + "could not be instantiated!");
            }
        }

        if (contextListeners.isEmpty() && sessionListeners.isEmpty() && requestListeners.isEmpty()) {
            LOGGER.log(Level.INFO, "WebListeners were not mapped!");
        }
        checkWebBeanConstraint(annotations, "@WebListener");
        checkAuthBeanConstraint(annotations, "@WebListener");
        checkRequestPathConstraint(annotations, "@WebListener");
        checkAsyncBeanConstraint(annotations, "@WebListener");
    }

    private void initAnnotatedWebSecurities(Reflections reflections) {
        Set<Class<?>> annotations = reflections.getTypesAnnotatedWith(WebSecurity.class);

        for (Class<?> clazz : annotations) {
            try {
                WebSecurity webSecurity = clazz.getAnnotation(WebSecurity.class);
                if (securityListeners.isEmpty()) {
                    LOGGER.log(Level.INFO, "Mapping WebSecurity class [" + clazz + "]");

                    if (!CsrfRequestListener.class.isAssignableFrom(clazz)) {
                        throw new RuntimeException("Mapped WebSecurity class [" + clazz + "] "
                                + "must implement CsrfRequestListener interface");
                    }
                    if (webSecurity.secretKey().length() != CsrfEncrypter.CYPHER_KEY_LENGTH) {
                        throw new RuntimeException("Mapped WebSecurity annotation for class [" + clazz + "] must "
                                + "have its secretKey value with [" + CsrfEncrypter.CYPHER_KEY_LENGTH
                                + "] characters");
                    }

                    HELPER.setBeanFields(clazz);
                    HELPER.setBeanMethods(clazz);
                    securityListeners.put(HELPER.getClassName(webSecurity, clazz), clazz);
                } else {
                    throw new RuntimeException("Only one WebSecurity can be declared! Skipping class " + clazz);
                }
            } catch (Exception ex) {
                LOGGER.log(Level.SEVERE,
                        "WebSecurity class [" + clazz.getName() + "] " + "could not be instantiated!");
            }
        }

        if (securityListeners.isEmpty()) {
            LOGGER.log(Level.INFO, "WebSecurities were not mapped!");
        }
        checkRequestPathConstraint(annotations, "@WebSecurity");
        checkAsyncBeanConstraint(annotations, "@WebSecurity");
    }

    private void checkWebBeanConstraint(Set<Class<?>> resources, String resourceName) {
        for (Class<?> clazz : resources) {
            for (Class<?> webClazz : webBeans.values()) {
                if (webClazz == clazz) {
                    LOGGER.log(Level.SEVERE,
                            "@WebBean class [" + webClazz + "] cannot be annotated with " + resourceName);
                }
            }
        }
    }

    private void checkAuthBeanConstraint(Set<Class<?>> resources, String resourceName) {
        for (Class<?> clazz : resources) {
            for (Class<?> authClazz : authBeans.values()) {
                if (authClazz == clazz) {
                    LOGGER.log(Level.SEVERE,
                            "@AuthBean class [" + authClazz + "] cannot be annotated with " + resourceName);
                }
            }
        }
    }

    private void checkRequestPathConstraint(Set<Class<?>> resources, String resourceName) {
        for (Class<?> clazz : resources) {
            for (Class<?> pathClazz : requestPaths.values()) {
                if (pathClazz == clazz) {
                    LOGGER.log(Level.SEVERE,
                            "@RequestPath class [" + pathClazz + "] cannot be annotated with " + resourceName);
                }
            }
        }
    }

    private void checkAsyncBeanConstraint(Set<Class<?>> resources, String resourceName) {
        for (Class<?> clazz : resources) {
            for (Class<?> asyncClazz : asyncBeans.values()) {
                if (asyncClazz == clazz) {
                    LOGGER.log(Level.SEVERE,
                            "@AsyncBean class [" + asyncClazz + "] cannot be annotated with " + resourceName);
                }
            }
        }
    }

    public String getForwardPath(String path) {
        if (path != null) {
            return forwardPaths.get(path);
        }
        return path;
    }

    AnnotatedAction getAnnotatedAction(String id) {
        if (id != null) {
            return annotatedActions.get(id);
        }
        return null;
    }

    List<AnnotatedFunction> getAnnotatedFunctions(String path) {
        if (path != null) {
            List<AnnotatedFunction> functions = annotatedFunctions.get(path);
            return functions != null ? functions : Collections.EMPTY_LIST;
        }
        return Collections.EMPTY_LIST;
    }

    private void initForwardPaths(ServletContext servletContext) {
        lookupInResourcePath(servletContext, Constants.PATH_SEPARATOR);
        overrideForwardPaths();
    }

    private void overrideForwardPaths() {
        for (UrlPattern urlPattern : CONFIG.getContent().getUrlPatterns()) {

            if (StringUtils.isNotBlank(urlPattern.getJsp())) {
                String prevJsp = forwardPaths.put(urlPattern.getUrl(), urlPattern.getJsp());

                if (prevJsp != null) {
                    LOGGER.log(Level.INFO, "Overriding path mapping [" + urlPattern.getUrl() + "] from [" + prevJsp
                            + "] " + "to [" + urlPattern.getJsp() + "]");
                } else {
                    LOGGER.log(Level.INFO,
                            "Mapping path [" + urlPattern.getUrl() + "] to [" + urlPattern.getJsp() + "]");
                }
            }
        }
    }

    private void lookupInResourcePath(ServletContext servletContext, String path) {
        Set<String> resources = servletContext.getResourcePaths(path);
        if (resources != null) {
            for (String res : resources) {
                if (res.endsWith(".jsp") || res.endsWith(".jspf") || res.endsWith(".html")) {
                    String[] bars = res.split(Constants.PATH_SEPARATOR);
                    if (res.endsWith(".jspf")) {

                        // Save the entire resource path to capture it later when reading JSP include tags
                        forwardPaths.put(res, res);
                    } else {
                        forwardPaths.put(Constants.PATH_SEPARATOR
                                + bars[bars.length - 1].replace(".jsp", "").replace(".html", ""), res);
                    }
                } else {
                    lookupInResourcePath(servletContext, res);
                }
            }
        }
    }

    private void checkWebXmlPath(ServletContext servletContext) {
        try {
            URL webXml = servletContext.getResource("/WEB-INF/web.xml");
            if (webXml != null) {
                throw new RuntimeException("JSmart framework is not compatible with [/WEB-INF/web.xml] file. "
                        + "Please remove the web.xml and compile your project with [failOnMissingWebXml=false]");
            }
        } catch (MalformedURLException ex) {
            LOGGER.log(Level.WARNING, "/WEB-INF/web.xml malformed Url: " + ex.getMessage());
        }
    }

    private void initJndiMapping() {
        try {
            String lookupName = CONFIG.getContent().getEjbLookup();
            initialContext = new InitialContext();

            // For glassfish implementation
            NamingEnumeration<Binding> bindList = initialContext.listBindings("");
            while (bindList.hasMore()) {
                Binding bind = bindList.next();
                if (bind != null && ("java:" + lookupName).equals(bind.getName())
                        && bind.getObject() instanceof Context) {
                    lookupInContext((Context) bind.getObject(), "java:" + lookupName);
                }
            }

            // For Jboss implementation
            if (jndiMapping.isEmpty()) {
                lookupInContext((Context) initialContext.lookup("java:" + lookupName), "java:" + lookupName);
            }
        } catch (Exception ex) {
            LOGGER.log(Level.WARNING, "JNDI for EJB mapping could not be initialized: " + ex.getMessage());
        }
    }

    private void lookupInContext(Context context, String prefix) {
        try {
            prefix += "/";
            NamingEnumeration<Binding> bindList = context.listBindings("");
            while (bindList.hasMore()) {
                Binding bind = bindList.next();
                if (bind != null) {
                    if (bind.getObject() instanceof Context) {
                        lookupInContext((Context) bind.getObject(), prefix + bind.getName());
                    }
                    String[] binds = bind.getName().split("!");
                    if (binds.length > 1) {
                        try {
                            jndiMapping.put(Class.forName(binds[1]), prefix + binds[0]);
                        } catch (Throwable ex) {
                            LOGGER.log(Level.WARNING,
                                    "Class could not be found for EJB mapping: " + ex.getMessage());
                        }
                    }
                }
            }
        } catch (Throwable ex) {
            LOGGER.log(Level.WARNING, "Bindings could not be found for EJB context: " + ex.getMessage());
        }
    }

    private void initJspPageBeans(ServletContext context) {
        for (UrlPattern urlPattern : CONFIG.getContent().getUrlPatterns()) {

            String path = HELPER.getCleanPath(urlPattern.getUrl());
            JspPageBean jspPageBean = new JspPageBean();

            readJspPageResource(context, path, jspPageBean);

            // Include the mapped bean containing function into jspPageBeans
            // so they can be initialized properly
            for (AnnotatedFunction annotatedFunction : getAnnotatedFunctions(path)) {
                jspPageBean.addBeanName(annotatedFunction.getClassName());
            }

            // Include the mapped bean containing exposed vars into jspPageBeans
            for (Class<?> clazz : HELPER.getExposeVarByPath(path)) {
                jspPageBean.addBeanName(HELPER.getBeanName(clazz));
            }
            jspPageBeans.put(path, jspPageBean);
        }
    }

    private void readJspPageResource(ServletContext context, String path, JspPageBean jspPageBean) {
        InputStream is = context.getResourceAsStream(getForwardPath(path));

        if (is != null) {
            Scanner fileScanner = new Scanner(is);
            Set<String> includes = new LinkedHashSet<>();

            try {
                String lineScan;
                while ((lineScan = fileScanner.findWithinHorizon(HANDLER_EL_PATTERN, 0)) != null) {

                    boolean hasInclude = false;
                    Matcher matcher = INCLUDE_PATTERN.matcher(lineScan);

                    while (matcher.find()) {
                        hasInclude = true;
                        includes.add(matcher.group(1));
                    }

                    if (hasInclude) {
                        continue;
                    }

                    matcher = ExpressionHandler.EL_PATTERN.matcher(lineScan);
                    while (matcher.find()) {
                        for (String name : matcher.group(1).split(Constants.SEPARATOR_REGEX)) {
                            if (webBeans.containsKey(name.trim())) {
                                jspPageBean.addBeanName(name.trim());
                            }
                        }
                    }

                    matcher = ExpressionHandler.JSP_PATTERN.matcher(lineScan);
                    while (matcher.find()) {
                        for (String name : matcher.group(1).split(Constants.SEPARATOR_REGEX)) {
                            if (webBeans.containsKey(name.trim())) {
                                jspPageBean.addBeanName(name.trim());
                            }
                        }
                    }

                    matcher = ExpressionHandler.ID_PATTERN.matcher(lineScan);
                    while (matcher.find()) {
                        AnnotatedAction annotatedAction = getAnnotatedAction(matcher.group(1));
                        if (annotatedAction != null) {
                            jspPageBean.addBeanName(annotatedAction.getClassName());
                        }
                    }
                }
            } finally {
                fileScanner.close();
            }

            // Read include page resources
            for (String include : includes) {
                String includeOwner = getForwardPath(path);
                includeOwner = includeOwner.substring(0, includeOwner.lastIndexOf(Constants.PATH_SEPARATOR) + 1);

                include = getRelativeIncludePath(includeOwner, include);
                readJspPageResource(context, include, jspPageBean);
            }
        }
    }

    private String getRelativeIncludePath(String includeOwner, String include) {
        int index = 0;
        int pathSeparators = 0;

        while (index != -1) {
            index = include.indexOf(Constants.PREVIOUS_PATH, index);
            if (index != -1) {
                pathSeparators++;
                index += Constants.PREVIOUS_PATH.length();
            }
        }
        if (pathSeparators != 0) {
            String[] ownerPath = includeOwner.split(Constants.PATH_SEPARATOR);
            if (ownerPath.length > pathSeparators) {
                includeOwner = includeOwner.substring(0,
                        includeOwner.lastIndexOf(ownerPath[ownerPath.length - pathSeparators]));
            } else {
                includeOwner = Constants.PATH_SEPARATOR;
            }
        }
        if (include.startsWith(Constants.PATH_SEPARATOR)) {
            include = include.replaceFirst(Constants.PATH_SEPARATOR, "");
        }
        return includeOwner + include.replace(Constants.PREVIOUS_PATH, "");
    }

    private class JspPageBean {

        private Set<String> beanNames;

        public JspPageBean() {
            this.beanNames = new LinkedHashSet<>();
        }

        public Set<String> getBeanNames() {
            return beanNames;
        }

        public void addBeanName(String beanName) {
            this.beanNames.add(beanName);
        }
    }

    public static class AnnotatedFunction {

        private Function function;

        private Method method;

        private String className;

        private List<Arg> arguments;

        private String beanMethod;

        private String beforeSend;

        private String onSuccess;

        private String onComplete;

        private String onError;

        private String update;

        private ProduceType produces;

        public AnnotatedFunction(Method method, String className, List<Arg> arguments) {
            this.method = method;
            this.className = className;
            this.function = method.getAnnotation(Function.class);
            this.beanMethod = String.format(EL_PATTERN_FORMAT, className, method.getName());
            this.beforeSend = StringUtils.join(function.beforeSend(), ";");
            this.onSuccess = StringUtils.join(function.onSuccess(), ";");
            this.onComplete = StringUtils.join(function.onComplete(), ";");
            this.onError = StringUtils.join(function.onError(), ";");
            this.update = StringUtils.join(function.update(), ",");
            this.produces = function.produces();
            this.arguments = arguments;
        }

        public Function getFunction() {
            return function;
        }

        public String getFunctionName() {
            if (StringUtils.isBlank(function.name())) {
                return method.getName();
            }
            return function.name();
        }

        public List<Arg> getArguments() {
            return arguments;
        }

        public Class<?> getArgumentType(int index) {
            Class<?>[] parameters = method.getParameterTypes();
            if (parameters.length > index) {
                return parameters[index];
            }
            return null;
        }

        public String getClassName() {
            return className;
        }

        public String getBeanMethod() {
            return beanMethod;
        }

        public String getBeforeSend() {
            return beforeSend;
        }

        public String getOnSuccess() {
            return onSuccess;
        }

        public String getOnComplete() {
            return onComplete;
        }

        public String getOnError() {
            return onError;
        }

        public String getUpdate() {
            return update;
        }

        public ProduceType getProduces() {
            return produces;
        }

        public boolean hasProduces() {
            return produces != ProduceType.VOID;
        }
    }

    public static class AnnotatedAction {

        private Action action;

        private Method method;

        private List<Arg> arguments;

        private String className;

        private String beanMethod;

        private String beforeSend;

        private String onSuccess;

        private String onComplete;

        private String onError;

        private String update;

        private boolean skipValidation;

        public AnnotatedAction(Method method, String className, List<Arg> arguments) {
            this.method = method;
            this.className = className;
            this.action = method.getAnnotation(Action.class);
            this.beanMethod = String.format(EL_PATTERN_FORMAT, className, method.getName());
            this.beforeSend = StringUtils.join(action.beforeSend(), ";");
            this.onSuccess = StringUtils.join(action.onSuccess(), ";");
            this.onComplete = StringUtils.join(action.onComplete(), ";");
            this.onError = StringUtils.join(action.onError(), ";");
            this.update = StringUtils.join(action.update(), ",");
            this.arguments = arguments;
            this.skipValidation = action.skipValidation();
        }

        public Action getAction() {
            return action;
        }

        public List<Arg> getArguments() {
            return arguments;
        }

        public String getClassName() {
            return className;
        }

        public String getBeanMethod() {
            return beanMethod;
        }

        public String getBeforeSend() {
            return beforeSend;
        }

        public String getOnSuccess() {
            return onSuccess;
        }

        public String getOnComplete() {
            return onComplete;
        }

        public String getOnError() {
            return onError;
        }

        public String getUpdate() {
            return update;
        }

        public boolean isSkipValidation() {
            return skipValidation;
        }
    }
}