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

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
permissions
-rw-r--r--

implements ResponseTypes

     1 /*
     2  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
     3  * 
     4  * Copyright 2017 Mike Becker. All rights reserved.
     5  * 
     6  * Redistribution and use in source and binary forms, with or without
     7  * modification, are permitted provided that the following conditions are met:
     8  *
     9  *   1. Redistributions of source code must retain the above copyright
    10  *      notice, this list of conditions and the following disclaimer.
    11  *
    12  *   2. Redistributions in binary form must reproduce the above copyright
    13  *      notice, this list of conditions and the following disclaimer in the
    14  *      documentation and/or other materials provided with the distribution.
    15  *
    16  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
    17  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
    18  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
    19  * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
    20  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
    21  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
    22  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
    23  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
    24  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
    25  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
    26  * POSSIBILITY OF SUCH DAMAGE.
    27  * 
    28  */
    29 package de.uapcore.lightpit;
    31 import java.io.IOException;
    32 import java.lang.reflect.Method;
    33 import java.lang.reflect.Modifier;
    34 import java.util.HashMap;
    35 import java.util.Map;
    36 import java.util.Optional;
    37 import javax.servlet.ServletException;
    38 import javax.servlet.http.HttpServlet;
    39 import javax.servlet.http.HttpServletRequest;
    40 import javax.servlet.http.HttpServletResponse;
    41 import org.slf4j.Logger;
    42 import org.slf4j.LoggerFactory;
    44 /**
    45  * A special implementation of a HTTPServlet which is focused on implementing
    46  * the necessary functionality for {@link LightPITModule}s.
    47  */
    48 public abstract class AbstractLightPITServlet extends HttpServlet {
    50     private static final Logger LOG = LoggerFactory.getLogger(AbstractLightPITServlet.class);
    52     /**
    53      * Store a reference to the annotation for quicker access.
    54      */
    55     private Optional<LightPITModule> moduleInfo = Optional.empty();
    57     /**
    58      * The EL proxy is necessary, because the EL resolver cannot handle annotation properties.
    59      */
    60     private Optional<LightPITModule.ELProxy> moduleInfoELProxy = Optional.empty();
    63     @FunctionalInterface
    64     private static interface HandlerMethod {
    65         ResponseType apply(HttpServletRequest t, HttpServletResponse u) throws IOException, ServletException;
    66     }
    68     /**
    69      * Invocation mapping gathered from the {@link RequestMapping} annotations.
    70      */
    71     private final Map<HttpMethod, Map<String, HandlerMethod>> mappings = new HashMap<>();
    73     /**
    74      * Gives implementing modules access to the {@link ModuleManager}.
    75      * @return the module manager
    76      */
    77     protected final ModuleManager getModuleManager() {
    78         return (ModuleManager) getServletContext().getAttribute(ModuleManager.SC_ATTR_NAME);
    79     }
    81     private ResponseType invokeMapping(Method method, HttpServletRequest req, HttpServletResponse resp)
    82             throws IOException, ServletException {
    83         try {
    84             LOG.debug("invoke {}", method.getName());
    85             return (ResponseType) method.invoke(this, req, resp);
    86         } catch (ReflectiveOperationException | ClassCastException ex) {
    87             LOG.error(String.format("invocation of method %s failed", method.getName()), ex);
    88             resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
    89             return ResponseType.NONE;
    90         }
    91     }
    93     @Override
    94     public void init() throws ServletException {
    95         moduleInfo = Optional.ofNullable(this.getClass().getAnnotation(LightPITModule.class));
    96         moduleInfoELProxy = moduleInfo.map(LightPITModule.ELProxy::convert);
    98         if (moduleInfo.isPresent()) {
    99             scanForRequestMappings();
   100         }
   102         LOG.trace("{} initialized", getServletName());
   103     }
   105     private void scanForRequestMappings() {
   106         try {
   107             Method[] methods = getClass().getDeclaredMethods();
   108             for (Method method : methods) {
   109                 Optional<RequestMapping> mapping = Optional.ofNullable(method.getAnnotation(RequestMapping.class));
   110                 if (mapping.isPresent()) {
   111                     if (!Modifier.isPublic(method.getModifiers())) {
   112                         LOG.warn("{} is annotated with {} but is not public",
   113                                 method.getName(), RequestMapping.class.getSimpleName()
   114                         );
   115                         continue;
   116                     }
   117                     if (Modifier.isAbstract(method.getModifiers())) {
   118                         LOG.warn("{} is annotated with {} but is abstract",
   119                                 method.getName(), RequestMapping.class.getSimpleName()
   120                         );
   121                         continue;
   122                     }
   123                     if (!ResponseType.class.isAssignableFrom(method.getReturnType())) {
   124                         LOG.warn("{} is annotated with {} but has the wrong return type - 'ResponseType' required",
   125                                 method.getName(), RequestMapping.class.getSimpleName()
   126                         );
   127                         continue;
   128                     }
   130                     Class<?>[] params = method.getParameterTypes();
   131                     if (params.length == 2
   132                             && HttpServletRequest.class.isAssignableFrom(params[0])
   133                             && HttpServletResponse.class.isAssignableFrom(params[1])) {
   135                         if (mappings.computeIfAbsent(mapping.get().method(), k -> new HashMap<>()).
   136                                 putIfAbsent(mapping.get().requestPath(),
   137                                         (req, resp) -> invokeMapping(method, req, resp)) != null) {
   138                             LOG.warn("{} {} has multiple mappings",
   139                                     mapping.get().method(),
   140                                     mapping.get().requestPath()
   141                             );
   142                         }
   144                         LOG.info("{} {} maps to {}",
   145                                 mapping.get().method(),
   146                                 mapping.get().requestPath(),
   147                                 method.getName()
   148                         );
   149                     } else {
   150                         LOG.warn("{} is annotated with {} but has the wrong parameters - (HttpServletRequest,HttpServletResponse) required",
   151                                 method.getName(), RequestMapping.class.getSimpleName()
   152                         );
   153                     }
   154                 }
   155             }
   156         } catch (SecurityException ex) {
   157             LOG.error("Scan for request mappings on declared methods failed.", ex);
   158         }
   159     }
   161     @Override
   162     public void destroy() {
   163         mappings.clear();
   164         LOG.trace("{} destroyed", getServletName());
   165     }
   168     /**
   169      * Sets several requests attributes, that can be used by the JSP.
   170      * 
   171      * @param req the servlet request object
   172      * @see Constants#REQ_ATTR_PATH
   173      * @see Constants#REQ_ATTR_MODULE_CLASSNAME
   174      * @see Constants#REQ_ATTR_MODULE_INFO
   175      */
   176     private void setGenericRequestAttributes(HttpServletRequest req) {
   177         req.setAttribute(Constants.REQ_ATTR_PATH, Functions.fullPath(req));
   179         req.setAttribute(Constants.REQ_ATTR_MODULE_CLASSNAME, this.getClass().getName());
   181         moduleInfoELProxy.ifPresent((proxy) -> req.setAttribute(Constants.REQ_ATTR_MODULE_INFO, proxy));
   182     }
   184     private void forwardToFullView(HttpServletRequest req, HttpServletResponse resp)
   185             throws IOException, ServletException {
   187         setGenericRequestAttributes(req);
   188         req.setAttribute(Constants.REQ_ATTR_MENU, getModuleManager().getMainMenu());
   189         req.getRequestDispatcher(Functions.jspPath("full.jsp")).forward(req, resp);
   190     }
   192     private Optional<HandlerMethod> findMapping(HttpMethod method, HttpServletRequest req) {
   193         return Optional.ofNullable(mappings.get(method)).map(
   194                 (rm) -> rm.get(Optional.ofNullable(req.getPathInfo()).orElse(""))
   195         );
   196     }
   198     private void forwardAsSepcified(ResponseType type, HttpServletRequest req, HttpServletResponse resp)
   199             throws ServletException, IOException {
   200         switch (type) {
   201             case NONE: return;
   202             case HTML_FULL:
   203                 forwardToFullView(req, resp);
   204                 return;
   205             // TODO: implement remaining response types
   206             default:
   207                 // this code should be unreachable
   208                 LOG.error("ResponseType switch is not exhaustive - this is a bug!");
   209                 throw new UnsupportedOperationException();
   210         }
   211     }
   213     private void doProcess(HttpMethod method, HttpServletRequest req, HttpServletResponse resp)
   214             throws ServletException, IOException {
   215         Optional<HandlerMethod> mapping = findMapping(method, req);
   216         if (mapping.isPresent()) {
   217             forwardAsSepcified(mapping.get().apply(req, resp), req, resp);
   218         } else {
   219             resp.sendError(HttpServletResponse.SC_NOT_FOUND);
   220         }
   221     }
   223     @Override
   224     protected final void doGet(HttpServletRequest req, HttpServletResponse resp)
   225             throws ServletException, IOException {
   226         doProcess(HttpMethod.GET, req, resp);
   227     }
   229     @Override
   230     protected final void doPost(HttpServletRequest req, HttpServletResponse resp)
   231             throws ServletException, IOException {
   232         doProcess(HttpMethod.POST, req, resp);
   233     }
   234 }

mercurial