--- a/src/java/de/uapcore/lightpit/AbstractLightPITServlet.java Sat Dec 16 20:19:28 2017 +0100 +++ b/src/java/de/uapcore/lightpit/AbstractLightPITServlet.java Sun Dec 17 01:45:28 2017 +0100 @@ -29,6 +29,12 @@ 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; @@ -43,9 +49,23 @@ 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 */ @@ -53,25 +73,114 @@ return (ModuleManager) getServletContext().getAttribute(ModuleManager.SC_ATTR_NAME); } - private void addPathInformation(HttpServletRequest req) { - final String path = req.getServletPath()+"/"+req.getPathInfo(); - req.setAttribute(Constants.REQ_ATTR_PATH, path); + 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 { - addPathInformation(req); - - final ModuleManager mm = getModuleManager(); - req.setAttribute(Constants.REQ_ATTR_MENU, mm.getMainMenu()); - + 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); } @@ -79,6 +188,8 @@ protected final void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + findMapping(HttpMethod.POST, req).ifPresent((consumer) -> consumer.accept(req, resp)); + forwardToFullView(req, resp); } }