Sat, 23 Dec 2017 17:28:19 +0100
implements ResponseTypes
1.1 --- a/src/java/de/uapcore/lightpit/AbstractLightPITServlet.java Sun Dec 17 01:45:28 2017 +0100 1.2 +++ b/src/java/de/uapcore/lightpit/AbstractLightPITServlet.java Sat Dec 23 17:28:19 2017 +0100 1.3 @@ -34,7 +34,6 @@ 1.4 import java.util.HashMap; 1.5 import java.util.Map; 1.6 import java.util.Optional; 1.7 -import java.util.function.BiConsumer; 1.8 import javax.servlet.ServletException; 1.9 import javax.servlet.http.HttpServlet; 1.10 import javax.servlet.http.HttpServletRequest; 1.11 @@ -60,10 +59,16 @@ 1.12 */ 1.13 private Optional<LightPITModule.ELProxy> moduleInfoELProxy = Optional.empty(); 1.14 1.15 + 1.16 + @FunctionalInterface 1.17 + private static interface HandlerMethod { 1.18 + ResponseType apply(HttpServletRequest t, HttpServletResponse u) throws IOException, ServletException; 1.19 + } 1.20 + 1.21 /** 1.22 * Invocation mapping gathered from the {@link RequestMapping} annotations. 1.23 */ 1.24 - private final Map<HttpMethod, Map<String, BiConsumer<HttpServletRequest, HttpServletResponse>>> mappings = new HashMap<>(); 1.25 + private final Map<HttpMethod, Map<String, HandlerMethod>> mappings = new HashMap<>(); 1.26 1.27 /** 1.28 * Gives implementing modules access to the {@link ModuleManager}. 1.29 @@ -73,12 +78,15 @@ 1.30 return (ModuleManager) getServletContext().getAttribute(ModuleManager.SC_ATTR_NAME); 1.31 } 1.32 1.33 - private void invokeMapping(Method method, HttpServletRequest req, HttpServletResponse resp) { 1.34 + private ResponseType invokeMapping(Method method, HttpServletRequest req, HttpServletResponse resp) 1.35 + throws IOException, ServletException { 1.36 try { 1.37 LOG.debug("invoke {}", method.getName()); 1.38 - method.invoke(this, req, resp); 1.39 - } catch (ReflectiveOperationException ex) { 1.40 + return (ResponseType) method.invoke(this, req, resp); 1.41 + } catch (ReflectiveOperationException | ClassCastException ex) { 1.42 LOG.error(String.format("invocation of method %s failed", method.getName()), ex); 1.43 + resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); 1.44 + return ResponseType.NONE; 1.45 } 1.46 } 1.47 1.48 @@ -88,6 +96,14 @@ 1.49 moduleInfoELProxy = moduleInfo.map(LightPITModule.ELProxy::convert); 1.50 1.51 if (moduleInfo.isPresent()) { 1.52 + scanForRequestMappings(); 1.53 + } 1.54 + 1.55 + LOG.trace("{} initialized", getServletName()); 1.56 + } 1.57 + 1.58 + private void scanForRequestMappings() { 1.59 + try { 1.60 Method[] methods = getClass().getDeclaredMethods(); 1.61 for (Method method : methods) { 1.62 Optional<RequestMapping> mapping = Optional.ofNullable(method.getAnnotation(RequestMapping.class)); 1.63 @@ -104,12 +120,18 @@ 1.64 ); 1.65 continue; 1.66 } 1.67 - 1.68 + if (!ResponseType.class.isAssignableFrom(method.getReturnType())) { 1.69 + LOG.warn("{} is annotated with {} but has the wrong return type - 'ResponseType' required", 1.70 + method.getName(), RequestMapping.class.getSimpleName() 1.71 + ); 1.72 + continue; 1.73 + } 1.74 + 1.75 Class<?>[] params = method.getParameterTypes(); 1.76 if (params.length == 2 1.77 && HttpServletRequest.class.isAssignableFrom(params[0]) 1.78 && HttpServletResponse.class.isAssignableFrom(params[1])) { 1.79 - 1.80 + 1.81 if (mappings.computeIfAbsent(mapping.get().method(), k -> new HashMap<>()). 1.82 putIfAbsent(mapping.get().requestPath(), 1.83 (req, resp) -> invokeMapping(method, req, resp)) != null) { 1.84 @@ -118,22 +140,22 @@ 1.85 mapping.get().requestPath() 1.86 ); 1.87 } 1.88 - 1.89 + 1.90 LOG.info("{} {} maps to {}", 1.91 mapping.get().method(), 1.92 mapping.get().requestPath(), 1.93 method.getName() 1.94 ); 1.95 } else { 1.96 - LOG.warn("{} is annotated with {} but has the wrong signature - (HttpServletRequest,HttpServletResponse) required", 1.97 + LOG.warn("{} is annotated with {} but has the wrong parameters - (HttpServletRequest,HttpServletResponse) required", 1.98 method.getName(), RequestMapping.class.getSimpleName() 1.99 ); 1.100 } 1.101 } 1.102 } 1.103 + } catch (SecurityException ex) { 1.104 + LOG.error("Scan for request mappings on declared methods failed.", ex); 1.105 } 1.106 - 1.107 - LOG.trace("{} initialized", getServletName()); 1.108 } 1.109 1.110 @Override 1.111 @@ -167,29 +189,46 @@ 1.112 req.getRequestDispatcher(Functions.jspPath("full.jsp")).forward(req, resp); 1.113 } 1.114 1.115 - private Optional<BiConsumer<HttpServletRequest, HttpServletResponse>> findMapping(HttpMethod method, HttpServletRequest req) { 1.116 + private Optional<HandlerMethod> findMapping(HttpMethod method, HttpServletRequest req) { 1.117 return Optional.ofNullable(mappings.get(method)).map( 1.118 (rm) -> rm.get(Optional.ofNullable(req.getPathInfo()).orElse("")) 1.119 ); 1.120 } 1.121 1.122 + private void forwardAsSepcified(ResponseType type, HttpServletRequest req, HttpServletResponse resp) 1.123 + throws ServletException, IOException { 1.124 + switch (type) { 1.125 + case NONE: return; 1.126 + case HTML_FULL: 1.127 + forwardToFullView(req, resp); 1.128 + return; 1.129 + // TODO: implement remaining response types 1.130 + default: 1.131 + // this code should be unreachable 1.132 + LOG.error("ResponseType switch is not exhaustive - this is a bug!"); 1.133 + throw new UnsupportedOperationException(); 1.134 + } 1.135 + } 1.136 + 1.137 + private void doProcess(HttpMethod method, HttpServletRequest req, HttpServletResponse resp) 1.138 + throws ServletException, IOException { 1.139 + Optional<HandlerMethod> mapping = findMapping(method, req); 1.140 + if (mapping.isPresent()) { 1.141 + forwardAsSepcified(mapping.get().apply(req, resp), req, resp); 1.142 + } else { 1.143 + resp.sendError(HttpServletResponse.SC_NOT_FOUND); 1.144 + } 1.145 + } 1.146 + 1.147 @Override 1.148 protected final void doGet(HttpServletRequest req, HttpServletResponse resp) 1.149 throws ServletException, IOException { 1.150 - 1.151 - findMapping(HttpMethod.GET, req).ifPresent((consumer) -> consumer.accept(req, resp)); 1.152 - 1.153 - // TODO: let the invoked handler decide (signature must be changed from a BiConsumer to a BiFunction) 1.154 - // TODO: we should call a default handler, if no specific mapping could be found 1.155 - forwardToFullView(req, resp); 1.156 + doProcess(HttpMethod.GET, req, resp); 1.157 } 1.158 1.159 @Override 1.160 protected final void doPost(HttpServletRequest req, HttpServletResponse resp) 1.161 throws ServletException, IOException { 1.162 - 1.163 - findMapping(HttpMethod.POST, req).ifPresent((consumer) -> consumer.accept(req, resp)); 1.164 - 1.165 - forwardToFullView(req, resp); 1.166 + doProcess(HttpMethod.POST, req, resp); 1.167 } 1.168 }
2.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 2.2 +++ b/src/java/de/uapcore/lightpit/ResponseType.java Sat Dec 23 17:28:19 2017 +0100 2.3 @@ -0,0 +1,56 @@ 2.4 +/* 2.5 + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. 2.6 + * 2.7 + * Copyright 2017 Mike Becker. All rights reserved. 2.8 + * 2.9 + * Redistribution and use in source and binary forms, with or without 2.10 + * modification, are permitted provided that the following conditions are met: 2.11 + * 2.12 + * 1. Redistributions of source code must retain the above copyright 2.13 + * notice, this list of conditions and the following disclaimer. 2.14 + * 2.15 + * 2. Redistributions in binary form must reproduce the above copyright 2.16 + * notice, this list of conditions and the following disclaimer in the 2.17 + * documentation and/or other materials provided with the distribution. 2.18 + * 2.19 + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 2.20 + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 2.21 + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 2.22 + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 2.23 + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 2.24 + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 2.25 + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 2.26 + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 2.27 + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 2.28 + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 2.29 + * POSSIBILITY OF SUCH DAMAGE. 2.30 + * 2.31 + */ 2.32 +package de.uapcore.lightpit; 2.33 + 2.34 + 2.35 +public enum ResponseType { 2.36 + /** 2.37 + * Renders a full HTML view including the header. 2.38 + */ 2.39 + HTML_FULL, 2.40 + /** 2.41 + * Renders a HTML fragment only. 2.42 + * May be used for AJAX responses. 2.43 + */ 2.44 + HTML_FRAGMENT, 2.45 + /** 2.46 + * Returns a fragment with content type 'text/plain'. 2.47 + */ 2.48 + PLAIN, 2.49 + /** 2.50 + * Returns an object in JSON format and with content type 2.51 + * 'application/json'. 2.52 + */ 2.53 + JSON, 2.54 + /** 2.55 + * The handler already sent the output, nothing should be done 2.56 + * additionally by the Servlet. 2.57 + */ 2.58 + NONE 2.59 +}
3.1 --- a/src/java/de/uapcore/lightpit/modules/HomeModule.java Sun Dec 17 01:45:28 2017 +0100 3.2 +++ b/src/java/de/uapcore/lightpit/modules/HomeModule.java Sat Dec 23 17:28:19 2017 +0100 3.3 @@ -30,7 +30,12 @@ 3.4 3.5 import de.uapcore.lightpit.LightPITModule; 3.6 import de.uapcore.lightpit.AbstractLightPITServlet; 3.7 +import de.uapcore.lightpit.HttpMethod; 3.8 +import de.uapcore.lightpit.RequestMapping; 3.9 +import de.uapcore.lightpit.ResponseType; 3.10 import javax.servlet.annotation.WebServlet; 3.11 +import javax.servlet.http.HttpServletRequest; 3.12 +import javax.servlet.http.HttpServletResponse; 3.13 3.14 /** 3.15 * Entry point for the application. 3.16 @@ -45,4 +50,9 @@ 3.17 ) 3.18 public final class HomeModule extends AbstractLightPITServlet { 3.19 3.20 + @RequestMapping(method = HttpMethod.GET) 3.21 + public ResponseType handle(HttpServletRequest req, HttpServletResponse resp) { 3.22 + 3.23 + return ResponseType.HTML_FULL; 3.24 + } 3.25 }
4.1 --- a/src/java/de/uapcore/lightpit/modules/LanguageModule.java Sun Dec 17 01:45:28 2017 +0100 4.2 +++ b/src/java/de/uapcore/lightpit/modules/LanguageModule.java Sat Dec 23 17:28:19 2017 +0100 4.3 @@ -35,6 +35,7 @@ 4.4 import javax.servlet.http.HttpServletRequest; 4.5 import javax.servlet.http.HttpServletResponse; 4.6 import de.uapcore.lightpit.RequestMapping; 4.7 +import de.uapcore.lightpit.ResponseType; 4.8 4.9 4.10 @LightPITModule( 4.11 @@ -48,7 +49,8 @@ 4.12 public final class LanguageModule extends AbstractLightPITServlet { 4.13 4.14 @RequestMapping(method = HttpMethod.GET) 4.15 - public void handle(HttpServletRequest req, HttpServletResponse resp) { 4.16 + public ResponseType handle(HttpServletRequest req, HttpServletResponse resp) { 4.17 4.18 + return ResponseType.HTML_FULL; 4.19 } 4.20 }
5.1 --- a/src/java/de/uapcore/lightpit/modules/VersionsModule.java Sun Dec 17 01:45:28 2017 +0100 5.2 +++ b/src/java/de/uapcore/lightpit/modules/VersionsModule.java Sat Dec 23 17:28:19 2017 +0100 5.3 @@ -30,7 +30,12 @@ 5.4 5.5 import de.uapcore.lightpit.LightPITModule; 5.6 import de.uapcore.lightpit.AbstractLightPITServlet; 5.7 +import de.uapcore.lightpit.HttpMethod; 5.8 +import de.uapcore.lightpit.RequestMapping; 5.9 +import de.uapcore.lightpit.ResponseType; 5.10 import javax.servlet.annotation.WebServlet; 5.11 +import javax.servlet.http.HttpServletRequest; 5.12 +import javax.servlet.http.HttpServletResponse; 5.13 5.14 5.15 @LightPITModule( 5.16 @@ -42,5 +47,9 @@ 5.17 urlPatterns = "/versions/*" 5.18 ) 5.19 public final class VersionsModule extends AbstractLightPITServlet { 5.20 - 5.21 + @RequestMapping(method = HttpMethod.GET) 5.22 + public ResponseType handle(HttpServletRequest req, HttpServletResponse resp) { 5.23 + 5.24 + return ResponseType.HTML_FULL; 5.25 + } 5.26 }