1.1 --- a/src/java/de/uapcore/lightpit/AbstractLightPITServlet.java Sun Apr 08 16:51:15 2018 +0200 1.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 1.3 @@ -1,296 +0,0 @@ 1.4 -/* 1.5 - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. 1.6 - * 1.7 - * Copyright 2018 Mike Becker. All rights reserved. 1.8 - * 1.9 - * Redistribution and use in source and binary forms, with or without 1.10 - * modification, are permitted provided that the following conditions are met: 1.11 - * 1.12 - * 1. Redistributions of source code must retain the above copyright 1.13 - * notice, this list of conditions and the following disclaimer. 1.14 - * 1.15 - * 2. Redistributions in binary form must reproduce the above copyright 1.16 - * notice, this list of conditions and the following disclaimer in the 1.17 - * documentation and/or other materials provided with the distribution. 1.18 - * 1.19 - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 1.20 - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 1.21 - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 1.22 - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 1.23 - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 1.24 - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 1.25 - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 1.26 - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 1.27 - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 1.28 - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 1.29 - * POSSIBILITY OF SUCH DAMAGE. 1.30 - * 1.31 - */ 1.32 -package de.uapcore.lightpit; 1.33 - 1.34 -import java.io.IOException; 1.35 -import java.lang.reflect.Method; 1.36 -import java.lang.reflect.Modifier; 1.37 -import java.util.Arrays; 1.38 -import java.util.HashMap; 1.39 -import java.util.List; 1.40 -import java.util.Locale; 1.41 -import java.util.Map; 1.42 -import java.util.Optional; 1.43 -import javax.servlet.ServletException; 1.44 -import javax.servlet.http.HttpServlet; 1.45 -import javax.servlet.http.HttpServletRequest; 1.46 -import javax.servlet.http.HttpServletResponse; 1.47 -import javax.servlet.http.HttpSession; 1.48 -import org.slf4j.Logger; 1.49 -import org.slf4j.LoggerFactory; 1.50 - 1.51 -/** 1.52 - * A special implementation of a HTTPServlet which is focused on implementing 1.53 - * the necessary functionality for {@link LightPITModule}s. 1.54 - */ 1.55 -public abstract class AbstractLightPITServlet extends HttpServlet { 1.56 - 1.57 - private static final Logger LOG = LoggerFactory.getLogger(AbstractLightPITServlet.class); 1.58 - 1.59 - private static final String HTML_FULL_DISPATCHER = Functions.jspPath("html_full"); 1.60 - 1.61 - /** 1.62 - * Store a reference to the annotation for quicker access. 1.63 - */ 1.64 - private Optional<LightPITModule> moduleInfo = Optional.empty(); 1.65 - 1.66 - /** 1.67 - * The EL proxy is necessary, because the EL resolver cannot handle annotation properties. 1.68 - */ 1.69 - private Optional<LightPITModule.ELProxy> moduleInfoELProxy = Optional.empty(); 1.70 - 1.71 - 1.72 - @FunctionalInterface 1.73 - private static interface HandlerMethod { 1.74 - ResponseType apply(HttpServletRequest t, HttpServletResponse u) throws IOException, ServletException; 1.75 - } 1.76 - 1.77 - /** 1.78 - * Invocation mapping gathered from the {@link RequestMapping} annotations. 1.79 - * 1.80 - * Paths in this map must always start with a leading slash, although 1.81 - * the specification in the annotation must not start with a leading slash. 1.82 - * 1.83 - * The reason for this is the different handling of empty paths in 1.84 - * {@link HttpServletRequest#getPathInfo()}. 1.85 - */ 1.86 - private final Map<HttpMethod, Map<String, HandlerMethod>> mappings = new HashMap<>(); 1.87 - 1.88 - /** 1.89 - * Gives implementing modules access to the {@link ModuleManager}. 1.90 - * @return the module manager 1.91 - */ 1.92 - protected final ModuleManager getModuleManager() { 1.93 - return (ModuleManager) getServletContext().getAttribute(ModuleManager.SC_ATTR_NAME); 1.94 - } 1.95 - 1.96 - /** 1.97 - * Gives implementing modules access to the {@link DatabaseFacade}. 1.98 - * @return the database facade 1.99 - */ 1.100 - protected final DatabaseFacade getDatabaseFacade() { 1.101 - return (DatabaseFacade) getServletContext().getAttribute(DatabaseFacade.SC_ATTR_NAME); 1.102 - } 1.103 - 1.104 - private ResponseType invokeMapping(Method method, HttpServletRequest req, HttpServletResponse resp) 1.105 - throws IOException, ServletException { 1.106 - try { 1.107 - LOG.trace("invoke {}#{}", method.getDeclaringClass().getName(), method.getName()); 1.108 - return (ResponseType) method.invoke(this, req, resp); 1.109 - } catch (ReflectiveOperationException | ClassCastException ex) { 1.110 - LOG.error(String.format("invocation of method %s failed", method.getName()), ex); 1.111 - resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); 1.112 - return ResponseType.NONE; 1.113 - } 1.114 - } 1.115 - 1.116 - @Override 1.117 - public void init() throws ServletException { 1.118 - moduleInfo = Optional.ofNullable(this.getClass().getAnnotation(LightPITModule.class)); 1.119 - moduleInfoELProxy = moduleInfo.map(LightPITModule.ELProxy::convert); 1.120 - 1.121 - if (moduleInfo.isPresent()) { 1.122 - scanForRequestMappings(); 1.123 - } 1.124 - 1.125 - LOG.trace("{} initialized", getServletName()); 1.126 - } 1.127 - 1.128 - private void scanForRequestMappings() { 1.129 - try { 1.130 - Method[] methods = getClass().getDeclaredMethods(); 1.131 - for (Method method : methods) { 1.132 - Optional<RequestMapping> mapping = Optional.ofNullable(method.getAnnotation(RequestMapping.class)); 1.133 - if (mapping.isPresent()) { 1.134 - if (!Modifier.isPublic(method.getModifiers())) { 1.135 - LOG.warn("{} is annotated with {} but is not public", 1.136 - method.getName(), RequestMapping.class.getSimpleName() 1.137 - ); 1.138 - continue; 1.139 - } 1.140 - if (Modifier.isAbstract(method.getModifiers())) { 1.141 - LOG.warn("{} is annotated with {} but is abstract", 1.142 - method.getName(), RequestMapping.class.getSimpleName() 1.143 - ); 1.144 - continue; 1.145 - } 1.146 - if (!ResponseType.class.isAssignableFrom(method.getReturnType())) { 1.147 - LOG.warn("{} is annotated with {} but has the wrong return type - 'ResponseType' required", 1.148 - method.getName(), RequestMapping.class.getSimpleName() 1.149 - ); 1.150 - continue; 1.151 - } 1.152 - 1.153 - Class<?>[] params = method.getParameterTypes(); 1.154 - if (params.length == 2 1.155 - && HttpServletRequest.class.isAssignableFrom(params[0]) 1.156 - && HttpServletResponse.class.isAssignableFrom(params[1])) { 1.157 - 1.158 - final String requestPath = "/"+mapping.get().requestPath(); 1.159 - 1.160 - if (mappings.computeIfAbsent(mapping.get().method(), k -> new HashMap<>()). 1.161 - putIfAbsent(requestPath, 1.162 - (req, resp) -> invokeMapping(method, req, resp)) != null) { 1.163 - LOG.warn("{} {} has multiple mappings", 1.164 - mapping.get().method(), 1.165 - mapping.get().requestPath() 1.166 - ); 1.167 - } 1.168 - 1.169 - LOG.debug("{} {} maps to {}::{}", 1.170 - mapping.get().method(), 1.171 - requestPath, 1.172 - getClass().getSimpleName(), 1.173 - method.getName() 1.174 - ); 1.175 - } else { 1.176 - LOG.warn("{} is annotated with {} but has the wrong parameters - (HttpServletRequest,HttpServletResponse) required", 1.177 - method.getName(), RequestMapping.class.getSimpleName() 1.178 - ); 1.179 - } 1.180 - } 1.181 - } 1.182 - } catch (SecurityException ex) { 1.183 - LOG.error("Scan for request mappings on declared methods failed.", ex); 1.184 - } 1.185 - } 1.186 - 1.187 - @Override 1.188 - public void destroy() { 1.189 - mappings.clear(); 1.190 - LOG.trace("{} destroyed", getServletName()); 1.191 - } 1.192 - 1.193 - /** 1.194 - * Sets the name of the dynamic fragment. 1.195 - * 1.196 - * It is sufficient to specify the name without any extension. The extension 1.197 - * is added automatically if not specified. 1.198 - * 1.199 - * The fragment must be located in the dynamic fragments folder. 1.200 - * 1.201 - * @param req the servlet request object 1.202 - * @param fragmentName the name of the fragment 1.203 - * @see Constants#DYN_FRAGMENT_PATH_PREFIX 1.204 - */ 1.205 - public void setDynamicFragment(HttpServletRequest req, String fragmentName) { 1.206 - req.setAttribute(Constants.REQ_ATTR_FRAGMENT, Functions.dynFragmentPath(fragmentName)); 1.207 - } 1.208 - 1.209 - /** 1.210 - * Specifies the name of an additional stylesheet used by the module. 1.211 - * 1.212 - * Setting an additional stylesheet is optional, but quite common for HTML 1.213 - * output. 1.214 - * 1.215 - * It is sufficient to specify the name without any extension. The extension 1.216 - * is added automatically if not specified. 1.217 - * 1.218 - * @param req the servlet request object 1.219 - * @param stylesheet the name of the stylesheet 1.220 - */ 1.221 - public void setStylesheet(HttpServletRequest req, String stylesheet) { 1.222 - req.setAttribute(Constants.REQ_ATTR_STYLESHEET, Functions.enforceExt(stylesheet, ".css")); 1.223 - } 1.224 - 1.225 - private void forwardToFullView(HttpServletRequest req, HttpServletResponse resp) 1.226 - throws IOException, ServletException { 1.227 - 1.228 - req.setAttribute(Constants.REQ_ATTR_MENU, getModuleManager().getMainMenu(getDatabaseFacade())); 1.229 - req.getRequestDispatcher(HTML_FULL_DISPATCHER).forward(req, resp); 1.230 - } 1.231 - 1.232 - private Optional<HandlerMethod> findMapping(HttpMethod method, HttpServletRequest req) { 1.233 - return Optional.ofNullable(mappings.get(method)).map( 1.234 - (rm) -> rm.get(Optional.ofNullable(req.getPathInfo()).orElse("/")) 1.235 - ); 1.236 - } 1.237 - 1.238 - private void forwardAsSepcified(ResponseType type, HttpServletRequest req, HttpServletResponse resp) 1.239 - throws ServletException, IOException { 1.240 - switch (type) { 1.241 - case NONE: return; 1.242 - case HTML_FULL: 1.243 - forwardToFullView(req, resp); 1.244 - return; 1.245 - // TODO: implement remaining response types 1.246 - default: 1.247 - // this code should be unreachable 1.248 - LOG.error("ResponseType switch is not exhaustive - this is a bug!"); 1.249 - throw new UnsupportedOperationException(); 1.250 - } 1.251 - } 1.252 - 1.253 - private void doProcess(HttpMethod method, HttpServletRequest req, HttpServletResponse resp) 1.254 - throws ServletException, IOException { 1.255 - 1.256 - // Synchronize module information with database 1.257 - getModuleManager().syncWithDatabase(getDatabaseFacade()); 1.258 - 1.259 - // choose the requested language as session language (if available) or fall back to english, otherwise 1.260 - HttpSession session = req.getSession(); 1.261 - if (session.getAttribute(Constants.SESSION_ATTR_LANGUAGE) == null) { 1.262 - Optional<List<String>> availableLanguages = Functions.availableLanguages(getServletContext()).map(Arrays::asList); 1.263 - Optional<Locale> reqLocale = Optional.of(req.getLocale()); 1.264 - Locale sessionLocale = reqLocale.filter((rl) -> availableLanguages.map((al) -> al.contains(rl.getLanguage())).orElse(false)).orElse(Locale.ENGLISH); 1.265 - session.setAttribute(Constants.SESSION_ATTR_LANGUAGE, sessionLocale); 1.266 - LOG.debug("Settng language for new session {}: {}", session.getId(), sessionLocale.getDisplayLanguage()); 1.267 - } else { 1.268 - Locale sessionLocale = (Locale) session.getAttribute(Constants.SESSION_ATTR_LANGUAGE); 1.269 - resp.setLocale(sessionLocale); 1.270 - LOG.trace("Continuing session {} with language {}", session.getId(), sessionLocale); 1.271 - } 1.272 - 1.273 - // set some internal request attributes 1.274 - req.setAttribute(Constants.REQ_ATTR_PATH, Functions.fullPath(req)); 1.275 - req.setAttribute(Constants.REQ_ATTR_MODULE_CLASSNAME, this.getClass().getName()); 1.276 - moduleInfoELProxy.ifPresent((proxy) -> req.setAttribute(Constants.REQ_ATTR_MODULE_INFO, proxy)); 1.277 - 1.278 - 1.279 - // call the handler, if available, or send an HTTP 404 error 1.280 - Optional<HandlerMethod> mapping = findMapping(method, req); 1.281 - if (mapping.isPresent()) { 1.282 - forwardAsSepcified(mapping.get().apply(req, resp), req, resp); 1.283 - } else { 1.284 - resp.sendError(HttpServletResponse.SC_NOT_FOUND); 1.285 - } 1.286 - } 1.287 - 1.288 - @Override 1.289 - protected final void doGet(HttpServletRequest req, HttpServletResponse resp) 1.290 - throws ServletException, IOException { 1.291 - doProcess(HttpMethod.GET, req, resp); 1.292 - } 1.293 - 1.294 - @Override 1.295 - protected final void doPost(HttpServletRequest req, HttpServletResponse resp) 1.296 - throws ServletException, IOException { 1.297 - doProcess(HttpMethod.POST, req, resp); 1.298 - } 1.299 -}