View Javadoc
1   package common.http.interceptor;
2   
3   import javax.servlet.ServletException;
4   import javax.servlet.ServletRequest;
5   import javax.servlet.ServletResponse;
6   import javax.servlet.http.HttpServlet;
7   import javax.servlet.http.HttpServletRequest;
8   import javax.servlet.http.HttpServletResponse;
9   import java.io.IOException;
10  import java.lang.reflect.Method;
11  import java.util.Arrays;
12  import java.util.Comparator;
13  import java.util.Map;
14  import java.util.Objects;
15  import java.util.concurrent.ConcurrentHashMap;
16  import java.util.stream.Stream;
17  
18  
19  /**
20   * Estende la classe astratta {@link HttpServlet} per fornire supporto al meccanismo degli interceptor.
21   *
22   * È possibile applicare gli interceptor in due modi:
23   * <ul>
24   *     <li>Applicando l'annotazione associata all'interceptor desiderato sul metodo "doX" desiderato</li>
25   *     <li>Applicando l'annotazione associata all'interceptor desiderato sulla classe interceptor. In questo modo,
26   *     l'interceptor sarà applicato a tutti i metodi "doX" sovrascritti.</li>
27   * </ul>
28   *
29   *
30   * <pre>
31  *  <code>
32   *     {@literal @}RequireAuthentication
33   *     {@literal @}EnableLogging(SEVERE)
34   *     private static class SampleInterceptableServlet extends InterceptableServlet{
35   *         {@literal @}Override
36   *         {@literal @}ErrorsAsJson
37   *         protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
38   *              ....
39   *         }
40   *     }
41   * </code>
42   * </pre>
43   * @see ServletInterceptor
44   * @see HttpServlet
45   */
46  public abstract class InterceptableServlet extends HttpServlet {
47      private static final Map<String, String> methods =
48              Map.of(
49                      "GET",     "doGet",
50                      "POST",    "doPost",
51                      "PUT",     "doPut",
52                      "TRACE",   "doTrace",
53                      "OPTIONS", "doOptions",
54                      "DELETE",  "doDelete",
55                      "HEAD",    "doHead"
56              );
57  
58      private final Map<String, HttpServletBiConsumer> chains = new ConcurrentHashMap<>();
59  
60      private ServletInterceptor<?>[] getInterceptors(String httpMethodName) {
61          String javaMethodName = methods.get(httpMethodName);
62          if(javaMethodName == null){
63              //Not a "doX" method
64              throw new IllegalArgumentException();
65          }
66  
67          Method method;
68          try {
69              method = this.getClass()
70                      .getDeclaredMethod(javaMethodName, HttpServletRequest.class, HttpServletResponse.class);
71          } catch (NoSuchMethodException e) {
72              //method has not been overriden by this class.
73              //return empty array
74              return new ServletInterceptor[]{};
75          }
76  
77  
78          //get annotations and their respective interceptor
79          return Stream.concat(Arrays.stream(getClass().getAnnotations()), Arrays.stream(method.getAnnotations()))
80                  .map(ServletInterceptorFactory::instantiate)
81                  .filter(Objects::nonNull)
82                  .sorted(Comparator.comparingInt(ServletInterceptor::priority)) //stable ordering
83                  .toArray(ServletInterceptor[]::new);
84      }
85  
86      private HttpServletBiConsumer buildChain(ServletInterceptor<?>[] interceptors, HttpServletBiConsumer target){
87  
88          //builds the chain backwards
89          HttpServletBiConsumer current = target;
90          for(int i = interceptors.length-1; i >= 0; i--){
91              ServletInterceptor<?> interceptor = interceptors[i];
92  
93              HttpServletBiConsumer next = current;
94              current = (req, res) -> interceptor.handle(req, res, next);
95          }
96          return current;
97      }
98  
99      //unambiguous super.service (there are 2 service methods. java lambdas hate that.)
100     private void superService(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
101         super.service(req, resp);
102     }
103 
104     @Override
105     protected final void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
106         String httpMethod = req.getMethod();
107         chains.computeIfAbsent(httpMethod, key -> buildChain(getInterceptors(key), this::superService)).handle(req,resp);
108     }
109 
110     @Override
111     public final void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
112         service((HttpServletRequest) req, (HttpServletResponse) res);
113     }
114 
115 }