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
     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  }

mercurial