implements ResponseTypes

Sat, 23 Dec 2017 17:28:19 +0100

author
Mike Becker <universe@uap-core.de>
date
Sat, 23 Dec 2017 17:28:19 +0100
changeset 12
005d27918b57
parent 11
737ab27e37b3
child 13
f4608ad6c947

implements ResponseTypes

src/java/de/uapcore/lightpit/AbstractLightPITServlet.java file | annotate | diff | comparison | revisions
src/java/de/uapcore/lightpit/ResponseType.java file | annotate | diff | comparison | revisions
src/java/de/uapcore/lightpit/modules/HomeModule.java file | annotate | diff | comparison | revisions
src/java/de/uapcore/lightpit/modules/LanguageModule.java file | annotate | diff | comparison | revisions
src/java/de/uapcore/lightpit/modules/VersionsModule.java file | annotate | diff | comparison | revisions
--- 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;
+    }
 }

mercurial