--- a/src/java/de/uapcore/lightpit/AbstractLightPITServlet.java Sun Dec 17 01:45:28 2017 +0100 +++ b/src/java/de/uapcore/lightpit/AbstractLightPITServlet.java Sat Dec 23 17:28:19 2017 +0100 @@ -34,7 +34,6 @@ 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; @@ -60,10 +59,16 @@ */ private Optional<LightPITModule.ELProxy> moduleInfoELProxy = Optional.empty(); + + @FunctionalInterface + private static interface HandlerMethod { + ResponseType apply(HttpServletRequest t, HttpServletResponse u) throws IOException, ServletException; + } + /** * Invocation mapping gathered from the {@link RequestMapping} annotations. */ - private final Map<HttpMethod, Map<String, BiConsumer<HttpServletRequest, HttpServletResponse>>> mappings = new HashMap<>(); + private final Map<HttpMethod, Map<String, HandlerMethod>> mappings = new HashMap<>(); /** * Gives implementing modules access to the {@link ModuleManager}. @@ -73,12 +78,15 @@ return (ModuleManager) getServletContext().getAttribute(ModuleManager.SC_ATTR_NAME); } - private void invokeMapping(Method method, HttpServletRequest req, HttpServletResponse resp) { + private ResponseType invokeMapping(Method method, HttpServletRequest req, HttpServletResponse resp) + throws IOException, ServletException { try { LOG.debug("invoke {}", method.getName()); - method.invoke(this, req, resp); - } catch (ReflectiveOperationException ex) { + return (ResponseType) method.invoke(this, req, resp); + } catch (ReflectiveOperationException | ClassCastException ex) { LOG.error(String.format("invocation of method %s failed", method.getName()), ex); + resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); + return ResponseType.NONE; } } @@ -88,6 +96,14 @@ moduleInfoELProxy = moduleInfo.map(LightPITModule.ELProxy::convert); if (moduleInfo.isPresent()) { + scanForRequestMappings(); + } + + LOG.trace("{} initialized", getServletName()); + } + + private void scanForRequestMappings() { + try { Method[] methods = getClass().getDeclaredMethods(); for (Method method : methods) { Optional<RequestMapping> mapping = Optional.ofNullable(method.getAnnotation(RequestMapping.class)); @@ -104,12 +120,18 @@ ); continue; } - + if (!ResponseType.class.isAssignableFrom(method.getReturnType())) { + LOG.warn("{} is annotated with {} but has the wrong return type - 'ResponseType' required", + 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) { @@ -118,22 +140,22 @@ 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", + LOG.warn("{} is annotated with {} but has the wrong parameters - (HttpServletRequest,HttpServletResponse) required", method.getName(), RequestMapping.class.getSimpleName() ); } } } + } catch (SecurityException ex) { + LOG.error("Scan for request mappings on declared methods failed.", ex); } - - LOG.trace("{} initialized", getServletName()); } @Override @@ -167,29 +189,46 @@ req.getRequestDispatcher(Functions.jspPath("full.jsp")).forward(req, resp); } - private Optional<BiConsumer<HttpServletRequest, HttpServletResponse>> findMapping(HttpMethod method, HttpServletRequest req) { + private Optional<HandlerMethod> findMapping(HttpMethod method, HttpServletRequest req) { return Optional.ofNullable(mappings.get(method)).map( (rm) -> rm.get(Optional.ofNullable(req.getPathInfo()).orElse("")) ); } + private void forwardAsSepcified(ResponseType type, HttpServletRequest req, HttpServletResponse resp) + throws ServletException, IOException { + switch (type) { + case NONE: return; + case HTML_FULL: + forwardToFullView(req, resp); + return; + // TODO: implement remaining response types + default: + // this code should be unreachable + LOG.error("ResponseType switch is not exhaustive - this is a bug!"); + throw new UnsupportedOperationException(); + } + } + + private void doProcess(HttpMethod method, HttpServletRequest req, HttpServletResponse resp) + throws ServletException, IOException { + Optional<HandlerMethod> mapping = findMapping(method, req); + if (mapping.isPresent()) { + forwardAsSepcified(mapping.get().apply(req, resp), req, resp); + } else { + resp.sendError(HttpServletResponse.SC_NOT_FOUND); + } + } + @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); + doProcess(HttpMethod.GET, 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); + doProcess(HttpMethod.POST, req, resp); } }