1.1 --- a/src/java/de/uapcore/lightpit/AbstractLightPITServlet.java Sat Dec 16 20:19:28 2017 +0100 1.2 +++ b/src/java/de/uapcore/lightpit/AbstractLightPITServlet.java Sun Dec 17 01:45:28 2017 +0100 1.3 @@ -29,6 +29,12 @@ 1.4 package de.uapcore.lightpit; 1.5 1.6 import java.io.IOException; 1.7 +import java.lang.reflect.Method; 1.8 +import java.lang.reflect.Modifier; 1.9 +import java.util.HashMap; 1.10 +import java.util.Map; 1.11 +import java.util.Optional; 1.12 +import java.util.function.BiConsumer; 1.13 import javax.servlet.ServletException; 1.14 import javax.servlet.http.HttpServlet; 1.15 import javax.servlet.http.HttpServletRequest; 1.16 @@ -43,9 +49,23 @@ 1.17 public abstract class AbstractLightPITServlet extends HttpServlet { 1.18 1.19 private static final Logger LOG = LoggerFactory.getLogger(AbstractLightPITServlet.class); 1.20 + 1.21 + /** 1.22 + * Store a reference to the annotation for quicker access. 1.23 + */ 1.24 + private Optional<LightPITModule> moduleInfo = Optional.empty(); 1.25 1.26 + /** 1.27 + * The EL proxy is necessary, because the EL resolver cannot handle annotation properties. 1.28 + */ 1.29 + private Optional<LightPITModule.ELProxy> moduleInfoELProxy = Optional.empty(); 1.30 1.31 /** 1.32 + * Invocation mapping gathered from the {@link RequestMapping} annotations. 1.33 + */ 1.34 + private final Map<HttpMethod, Map<String, BiConsumer<HttpServletRequest, HttpServletResponse>>> mappings = new HashMap<>(); 1.35 + 1.36 + /** 1.37 * Gives implementing modules access to the {@link ModuleManager}. 1.38 * @return the module manager 1.39 */ 1.40 @@ -53,25 +73,114 @@ 1.41 return (ModuleManager) getServletContext().getAttribute(ModuleManager.SC_ATTR_NAME); 1.42 } 1.43 1.44 - private void addPathInformation(HttpServletRequest req) { 1.45 - final String path = req.getServletPath()+"/"+req.getPathInfo(); 1.46 - req.setAttribute(Constants.REQ_ATTR_PATH, path); 1.47 + private void invokeMapping(Method method, HttpServletRequest req, HttpServletResponse resp) { 1.48 + try { 1.49 + LOG.debug("invoke {}", method.getName()); 1.50 + method.invoke(this, req, resp); 1.51 + } catch (ReflectiveOperationException ex) { 1.52 + LOG.error(String.format("invocation of method %s failed", method.getName()), ex); 1.53 + } 1.54 + } 1.55 + 1.56 + @Override 1.57 + public void init() throws ServletException { 1.58 + moduleInfo = Optional.ofNullable(this.getClass().getAnnotation(LightPITModule.class)); 1.59 + moduleInfoELProxy = moduleInfo.map(LightPITModule.ELProxy::convert); 1.60 + 1.61 + if (moduleInfo.isPresent()) { 1.62 + Method[] methods = getClass().getDeclaredMethods(); 1.63 + for (Method method : methods) { 1.64 + Optional<RequestMapping> mapping = Optional.ofNullable(method.getAnnotation(RequestMapping.class)); 1.65 + if (mapping.isPresent()) { 1.66 + if (!Modifier.isPublic(method.getModifiers())) { 1.67 + LOG.warn("{} is annotated with {} but is not public", 1.68 + method.getName(), RequestMapping.class.getSimpleName() 1.69 + ); 1.70 + continue; 1.71 + } 1.72 + if (Modifier.isAbstract(method.getModifiers())) { 1.73 + LOG.warn("{} is annotated with {} but is abstract", 1.74 + method.getName(), RequestMapping.class.getSimpleName() 1.75 + ); 1.76 + continue; 1.77 + } 1.78 + 1.79 + Class<?>[] params = method.getParameterTypes(); 1.80 + if (params.length == 2 1.81 + && HttpServletRequest.class.isAssignableFrom(params[0]) 1.82 + && HttpServletResponse.class.isAssignableFrom(params[1])) { 1.83 + 1.84 + if (mappings.computeIfAbsent(mapping.get().method(), k -> new HashMap<>()). 1.85 + putIfAbsent(mapping.get().requestPath(), 1.86 + (req, resp) -> invokeMapping(method, req, resp)) != null) { 1.87 + LOG.warn("{} {} has multiple mappings", 1.88 + mapping.get().method(), 1.89 + mapping.get().requestPath() 1.90 + ); 1.91 + } 1.92 + 1.93 + LOG.info("{} {} maps to {}", 1.94 + mapping.get().method(), 1.95 + mapping.get().requestPath(), 1.96 + method.getName() 1.97 + ); 1.98 + } else { 1.99 + LOG.warn("{} is annotated with {} but has the wrong signature - (HttpServletRequest,HttpServletResponse) required", 1.100 + method.getName(), RequestMapping.class.getSimpleName() 1.101 + ); 1.102 + } 1.103 + } 1.104 + } 1.105 + } 1.106 + 1.107 + LOG.trace("{} initialized", getServletName()); 1.108 + } 1.109 + 1.110 + @Override 1.111 + public void destroy() { 1.112 + mappings.clear(); 1.113 + LOG.trace("{} destroyed", getServletName()); 1.114 + } 1.115 + 1.116 + 1.117 + /** 1.118 + * Sets several requests attributes, that can be used by the JSP. 1.119 + * 1.120 + * @param req the servlet request object 1.121 + * @see Constants#REQ_ATTR_PATH 1.122 + * @see Constants#REQ_ATTR_MODULE_CLASSNAME 1.123 + * @see Constants#REQ_ATTR_MODULE_INFO 1.124 + */ 1.125 + private void setGenericRequestAttributes(HttpServletRequest req) { 1.126 + req.setAttribute(Constants.REQ_ATTR_PATH, Functions.fullPath(req)); 1.127 + 1.128 + req.setAttribute(Constants.REQ_ATTR_MODULE_CLASSNAME, this.getClass().getName()); 1.129 + 1.130 + moduleInfoELProxy.ifPresent((proxy) -> req.setAttribute(Constants.REQ_ATTR_MODULE_INFO, proxy)); 1.131 } 1.132 1.133 private void forwardToFullView(HttpServletRequest req, HttpServletResponse resp) 1.134 throws IOException, ServletException { 1.135 1.136 - addPathInformation(req); 1.137 - 1.138 - final ModuleManager mm = getModuleManager(); 1.139 - req.setAttribute(Constants.REQ_ATTR_MENU, mm.getMainMenu()); 1.140 - 1.141 + setGenericRequestAttributes(req); 1.142 + req.setAttribute(Constants.REQ_ATTR_MENU, getModuleManager().getMainMenu()); 1.143 req.getRequestDispatcher(Functions.jspPath("full.jsp")).forward(req, resp); 1.144 } 1.145 1.146 + private Optional<BiConsumer<HttpServletRequest, HttpServletResponse>> findMapping(HttpMethod method, HttpServletRequest req) { 1.147 + return Optional.ofNullable(mappings.get(method)).map( 1.148 + (rm) -> rm.get(Optional.ofNullable(req.getPathInfo()).orElse("")) 1.149 + ); 1.150 + } 1.151 + 1.152 @Override 1.153 protected final void doGet(HttpServletRequest req, HttpServletResponse resp) 1.154 throws ServletException, IOException { 1.155 + 1.156 + findMapping(HttpMethod.GET, req).ifPresent((consumer) -> consumer.accept(req, resp)); 1.157 + 1.158 + // TODO: let the invoked handler decide (signature must be changed from a BiConsumer to a BiFunction) 1.159 + // TODO: we should call a default handler, if no specific mapping could be found 1.160 forwardToFullView(req, resp); 1.161 } 1.162 1.163 @@ -79,6 +188,8 @@ 1.164 protected final void doPost(HttpServletRequest req, HttpServletResponse resp) 1.165 throws ServletException, IOException { 1.166 1.167 + findMapping(HttpMethod.POST, req).ifPresent((consumer) -> consumer.accept(req, resp)); 1.168 + 1.169 forwardToFullView(req, resp); 1.170 } 1.171 }