src/java/de/uapcore/lightpit/AbstractLightPITServlet.java

changeset 11
737ab27e37b3
parent 10
89e3e6e28b69
child 12
005d27918b57
--- a/src/java/de/uapcore/lightpit/AbstractLightPITServlet.java	Sat Dec 16 20:19:28 2017 +0100
+++ b/src/java/de/uapcore/lightpit/AbstractLightPITServlet.java	Sun Dec 17 01:45:28 2017 +0100
@@ -29,6 +29,12 @@
 package de.uapcore.lightpit;
 
 import java.io.IOException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+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;
@@ -43,9 +49,23 @@
 public abstract class AbstractLightPITServlet extends HttpServlet {
     
     private static final Logger LOG = LoggerFactory.getLogger(AbstractLightPITServlet.class);
+    
+    /**
+     * Store a reference to the annotation for quicker access.
+     */
+    private Optional<LightPITModule> moduleInfo = Optional.empty();
 
+    /**
+     * The EL proxy is necessary, because the EL resolver cannot handle annotation properties.
+     */
+    private Optional<LightPITModule.ELProxy> moduleInfoELProxy = Optional.empty();
     
     /**
+     * Invocation mapping gathered from the {@link RequestMapping} annotations.
+     */
+    private final Map<HttpMethod, Map<String, BiConsumer<HttpServletRequest, HttpServletResponse>>> mappings = new HashMap<>();
+
+    /**
      * Gives implementing modules access to the {@link ModuleManager}.
      * @return the module manager
      */
@@ -53,25 +73,114 @@
         return (ModuleManager) getServletContext().getAttribute(ModuleManager.SC_ATTR_NAME);
     }
     
-    private void addPathInformation(HttpServletRequest req) {
-        final String path = req.getServletPath()+"/"+req.getPathInfo();
-        req.setAttribute(Constants.REQ_ATTR_PATH, path);
+    private void invokeMapping(Method method, HttpServletRequest req, HttpServletResponse resp) {
+        try {
+            LOG.debug("invoke {}", method.getName());
+            method.invoke(this, req, resp);
+        } catch (ReflectiveOperationException ex) {
+            LOG.error(String.format("invocation of method %s failed", method.getName()), ex);
+        }
+    }
+
+    @Override
+    public void init() throws ServletException {
+        moduleInfo = Optional.ofNullable(this.getClass().getAnnotation(LightPITModule.class));
+        moduleInfoELProxy = moduleInfo.map(LightPITModule.ELProxy::convert);
+        
+        if (moduleInfo.isPresent()) {
+            Method[] methods = getClass().getDeclaredMethods();
+            for (Method method : methods) {
+                Optional<RequestMapping> mapping = Optional.ofNullable(method.getAnnotation(RequestMapping.class));
+                if (mapping.isPresent()) {
+                    if (!Modifier.isPublic(method.getModifiers())) {
+                        LOG.warn("{} is annotated with {} but is not public",
+                                method.getName(), RequestMapping.class.getSimpleName()
+                        );
+                        continue;
+                    }
+                    if (Modifier.isAbstract(method.getModifiers())) {
+                        LOG.warn("{} is annotated with {} but is abstract",
+                                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) {
+                            LOG.warn("{} {} has multiple mappings",
+                                    mapping.get().method(),
+                                    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",
+                                method.getName(), RequestMapping.class.getSimpleName()
+                        );
+                    }
+                }
+            }
+        }
+        
+        LOG.trace("{} initialized", getServletName());
+    }
+
+    @Override
+    public void destroy() {
+        mappings.clear();
+        LOG.trace("{} destroyed", getServletName());
+    }
+    
+    
+    /**
+     * Sets several requests attributes, that can be used by the JSP.
+     * 
+     * @param req the servlet request object
+     * @see Constants#REQ_ATTR_PATH
+     * @see Constants#REQ_ATTR_MODULE_CLASSNAME
+     * @see Constants#REQ_ATTR_MODULE_INFO
+     */
+    private void setGenericRequestAttributes(HttpServletRequest req) {
+        req.setAttribute(Constants.REQ_ATTR_PATH, Functions.fullPath(req));
+
+        req.setAttribute(Constants.REQ_ATTR_MODULE_CLASSNAME, this.getClass().getName());
+
+        moduleInfoELProxy.ifPresent((proxy) -> req.setAttribute(Constants.REQ_ATTR_MODULE_INFO, proxy));
     }
     
     private void forwardToFullView(HttpServletRequest req, HttpServletResponse resp)
             throws IOException, ServletException {
         
-        addPathInformation(req);
-        
-        final ModuleManager mm = getModuleManager();
-        req.setAttribute(Constants.REQ_ATTR_MENU, mm.getMainMenu());
-        
+        setGenericRequestAttributes(req);
+        req.setAttribute(Constants.REQ_ATTR_MENU, getModuleManager().getMainMenu());
         req.getRequestDispatcher(Functions.jspPath("full.jsp")).forward(req, resp);
     }
     
+    private Optional<BiConsumer<HttpServletRequest, HttpServletResponse>> findMapping(HttpMethod method, HttpServletRequest req) {
+        return Optional.ofNullable(mappings.get(method)).map(
+                (rm) -> rm.get(Optional.ofNullable(req.getPathInfo()).orElse(""))
+        );
+    }
+    
     @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);
     }
 
@@ -79,6 +188,8 @@
     protected final void doPost(HttpServletRequest req, HttpServletResponse resp)
             throws ServletException, IOException {
         
+        findMapping(HttpMethod.POST, req).ifPresent((consumer) -> consumer.accept(req, resp));
+        
         forwardToFullView(req, resp);
     }
 }

mercurial