Sat, 23 Dec 2017 17:28:19 +0100
implements ResponseTypes
--- 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); } }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/java/de/uapcore/lightpit/ResponseType.java Sat Dec 23 17:28:19 2017 +0100 @@ -0,0 +1,56 @@ +/* + * 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; + + +public enum ResponseType { + /** + * Renders a full HTML view including the header. + */ + HTML_FULL, + /** + * Renders a HTML fragment only. + * May be used for AJAX responses. + */ + HTML_FRAGMENT, + /** + * Returns a fragment with content type 'text/plain'. + */ + PLAIN, + /** + * Returns an object in JSON format and with content type + * 'application/json'. + */ + JSON, + /** + * The handler already sent the output, nothing should be done + * additionally by the Servlet. + */ + NONE +}
--- a/src/java/de/uapcore/lightpit/modules/HomeModule.java Sun Dec 17 01:45:28 2017 +0100 +++ b/src/java/de/uapcore/lightpit/modules/HomeModule.java Sat Dec 23 17:28:19 2017 +0100 @@ -30,7 +30,12 @@ import de.uapcore.lightpit.LightPITModule; import de.uapcore.lightpit.AbstractLightPITServlet; +import de.uapcore.lightpit.HttpMethod; +import de.uapcore.lightpit.RequestMapping; +import de.uapcore.lightpit.ResponseType; import javax.servlet.annotation.WebServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; /** * Entry point for the application. @@ -45,4 +50,9 @@ ) public final class HomeModule extends AbstractLightPITServlet { + @RequestMapping(method = HttpMethod.GET) + public ResponseType handle(HttpServletRequest req, HttpServletResponse resp) { + + return ResponseType.HTML_FULL; + } }
--- a/src/java/de/uapcore/lightpit/modules/LanguageModule.java Sun Dec 17 01:45:28 2017 +0100 +++ b/src/java/de/uapcore/lightpit/modules/LanguageModule.java Sat Dec 23 17:28:19 2017 +0100 @@ -35,6 +35,7 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import de.uapcore.lightpit.RequestMapping; +import de.uapcore.lightpit.ResponseType; @LightPITModule( @@ -48,7 +49,8 @@ public final class LanguageModule extends AbstractLightPITServlet { @RequestMapping(method = HttpMethod.GET) - public void handle(HttpServletRequest req, HttpServletResponse resp) { + public ResponseType handle(HttpServletRequest req, HttpServletResponse resp) { + return ResponseType.HTML_FULL; } }
--- a/src/java/de/uapcore/lightpit/modules/VersionsModule.java Sun Dec 17 01:45:28 2017 +0100 +++ b/src/java/de/uapcore/lightpit/modules/VersionsModule.java Sat Dec 23 17:28:19 2017 +0100 @@ -30,7 +30,12 @@ import de.uapcore.lightpit.LightPITModule; import de.uapcore.lightpit.AbstractLightPITServlet; +import de.uapcore.lightpit.HttpMethod; +import de.uapcore.lightpit.RequestMapping; +import de.uapcore.lightpit.ResponseType; import javax.servlet.annotation.WebServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; @LightPITModule( @@ -42,5 +47,9 @@ urlPatterns = "/versions/*" ) public final class VersionsModule extends AbstractLightPITServlet { - + @RequestMapping(method = HttpMethod.GET) + public ResponseType handle(HttpServletRequest req, HttpServletResponse resp) { + + return ResponseType.HTML_FULL; + } }