Sun, 17 Dec 2017 01:45:28 +0100
implements simple request mapper
/* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * * Copyright 2017 Mike Becker. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * */ package de.uapcore.lightpit; import java.io.IOException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.HashMap; import java.util.Map; import java.util.Optional; import java.util.function.BiConsumer; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * A special implementation of a HTTPServlet which is focused on implementing * the necessary functionality for {@link LightPITModule}s. */ public abstract class AbstractLightPITServlet extends HttpServlet { private static final Logger LOG = LoggerFactory.getLogger(AbstractLightPITServlet.class); /** * Store a reference to the annotation for quicker access. */ private Optional<LightPITModule> moduleInfo = Optional.empty(); /** * The EL proxy is necessary, because the EL resolver cannot handle annotation properties. */ private Optional<LightPITModule.ELProxy> moduleInfoELProxy = Optional.empty(); /** * Invocation mapping gathered from the {@link RequestMapping} annotations. */ private final Map<HttpMethod, Map<String, BiConsumer<HttpServletRequest, HttpServletResponse>>> mappings = new HashMap<>(); /** * Gives implementing modules access to the {@link ModuleManager}. * @return the module manager */ protected final ModuleManager getModuleManager() { return (ModuleManager) getServletContext().getAttribute(ModuleManager.SC_ATTR_NAME); } private void invokeMapping(Method method, HttpServletRequest req, HttpServletResponse resp) { try { LOG.debug("invoke {}", method.getName()); method.invoke(this, req, resp); } catch (ReflectiveOperationException ex) { LOG.error(String.format("invocation of method %s failed", method.getName()), ex); } } @Override public void init() throws ServletException { moduleInfo = Optional.ofNullable(this.getClass().getAnnotation(LightPITModule.class)); moduleInfoELProxy = moduleInfo.map(LightPITModule.ELProxy::convert); if (moduleInfo.isPresent()) { Method[] methods = getClass().getDeclaredMethods(); for (Method method : methods) { Optional<RequestMapping> mapping = Optional.ofNullable(method.getAnnotation(RequestMapping.class)); if (mapping.isPresent()) { if (!Modifier.isPublic(method.getModifiers())) { LOG.warn("{} is annotated with {} but is not public", method.getName(), RequestMapping.class.getSimpleName() ); continue; } if (Modifier.isAbstract(method.getModifiers())) { LOG.warn("{} is annotated with {} but is abstract", method.getName(), RequestMapping.class.getSimpleName() ); continue; } Class<?>[] params = method.getParameterTypes(); if (params.length == 2 && HttpServletRequest.class.isAssignableFrom(params[0]) && HttpServletResponse.class.isAssignableFrom(params[1])) { if (mappings.computeIfAbsent(mapping.get().method(), k -> new HashMap<>()). putIfAbsent(mapping.get().requestPath(), (req, resp) -> invokeMapping(method, req, resp)) != null) { LOG.warn("{} {} has multiple mappings", mapping.get().method(), mapping.get().requestPath() ); } LOG.info("{} {} maps to {}", mapping.get().method(), mapping.get().requestPath(), method.getName() ); } else { LOG.warn("{} is annotated with {} but has the wrong signature - (HttpServletRequest,HttpServletResponse) required", method.getName(), RequestMapping.class.getSimpleName() ); } } } } LOG.trace("{} initialized", getServletName()); } @Override public void destroy() { mappings.clear(); LOG.trace("{} destroyed", getServletName()); } /** * Sets several requests attributes, that can be used by the JSP. * * @param req the servlet request object * @see Constants#REQ_ATTR_PATH * @see Constants#REQ_ATTR_MODULE_CLASSNAME * @see Constants#REQ_ATTR_MODULE_INFO */ private void setGenericRequestAttributes(HttpServletRequest req) { req.setAttribute(Constants.REQ_ATTR_PATH, Functions.fullPath(req)); req.setAttribute(Constants.REQ_ATTR_MODULE_CLASSNAME, this.getClass().getName()); moduleInfoELProxy.ifPresent((proxy) -> req.setAttribute(Constants.REQ_ATTR_MODULE_INFO, proxy)); } private void forwardToFullView(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException { setGenericRequestAttributes(req); req.setAttribute(Constants.REQ_ATTR_MENU, getModuleManager().getMainMenu()); req.getRequestDispatcher(Functions.jspPath("full.jsp")).forward(req, resp); } private Optional<BiConsumer<HttpServletRequest, HttpServletResponse>> findMapping(HttpMethod method, HttpServletRequest req) { return Optional.ofNullable(mappings.get(method)).map( (rm) -> rm.get(Optional.ofNullable(req.getPathInfo()).orElse("")) ); } @Override protected final void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { findMapping(HttpMethod.GET, req).ifPresent((consumer) -> consumer.accept(req, resp)); // TODO: let the invoked handler decide (signature must be changed from a BiConsumer to a BiFunction) // TODO: we should call a default handler, if no specific mapping could be found forwardToFullView(req, resp); } @Override protected final void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { findMapping(HttpMethod.POST, req).ifPresent((consumer) -> consumer.accept(req, resp)); forwardToFullView(req, resp); } }