/*
* 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;
}
}
}
String responsePath = null;
if (submitExpr != null) {
responsePath = EXPRESSIONS.handleSubmitExpression(submitExpr, submitParam);
}
return responsePath;
}
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;
}
}
}