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 }