001package common.http.interceptor;
002
003import javax.servlet.ServletException;
004import javax.servlet.ServletRequest;
005import javax.servlet.ServletResponse;
006import javax.servlet.http.HttpServlet;
007import javax.servlet.http.HttpServletRequest;
008import javax.servlet.http.HttpServletResponse;
009import java.io.IOException;
010import java.lang.reflect.Method;
011import java.util.Arrays;
012import java.util.Comparator;
013import java.util.Map;
014import java.util.Objects;
015import java.util.concurrent.ConcurrentHashMap;
016import java.util.stream.Stream;
017
018
019/**
020 * Estende la classe astratta {@link HttpServlet} per fornire supporto al meccanismo degli interceptor.
021 *
022 * È possibile applicare gli interceptor in due modi:
023 * <ul>
024 *     <li>Applicando l'annotazione associata all'interceptor desiderato sul metodo "doX" desiderato</li>
025 *     <li>Applicando l'annotazione associata all'interceptor desiderato sulla classe interceptor. In questo modo,
026 *     l'interceptor sarà applicato a tutti i metodi "doX" sovrascritti.</li>
027 * </ul>
028 *
029 *
030 * <pre>
031*  <code>
032 *     {@literal @}RequireAuthentication
033 *     {@literal @}EnableLogging(SEVERE)
034 *     private static class SampleInterceptableServlet extends InterceptableServlet{
035 *         {@literal @}Override
036 *         {@literal @}ErrorsAsJson
037 *         protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
038 *              ....
039 *         }
040 *     }
041 * </code>
042 * </pre>
043 * @see ServletInterceptor
044 * @see HttpServlet
045 */
046public abstract class InterceptableServlet extends HttpServlet {
047    private static final Map<String, String> methods =
048            Map.of(
049                    "GET",     "doGet",
050                    "POST",    "doPost",
051                    "PUT",     "doPut",
052                    "TRACE",   "doTrace",
053                    "OPTIONS", "doOptions",
054                    "DELETE",  "doDelete",
055                    "HEAD",    "doHead"
056            );
057
058    private final Map<String, HttpServletBiConsumer> chains = new ConcurrentHashMap<>();
059
060    private ServletInterceptor<?>[] getInterceptors(String httpMethodName) {
061        String javaMethodName = methods.get(httpMethodName);
062        if(javaMethodName == null){
063            //Not a "doX" method
064            throw new IllegalArgumentException();
065        }
066
067        Method method;
068        try {
069            method = this.getClass()
070                    .getDeclaredMethod(javaMethodName, HttpServletRequest.class, HttpServletResponse.class);
071        } catch (NoSuchMethodException e) {
072            //method has not been overriden by this class.
073            //return empty array
074            return new ServletInterceptor[]{};
075        }
076
077
078        //get annotations and their respective interceptor
079        return Stream.concat(Arrays.stream(getClass().getAnnotations()), Arrays.stream(method.getAnnotations()))
080                .map(ServletInterceptorFactory::instantiate)
081                .filter(Objects::nonNull)
082                .sorted(Comparator.comparingInt(ServletInterceptor::priority)) //stable ordering
083                .toArray(ServletInterceptor[]::new);
084    }
085
086    private HttpServletBiConsumer buildChain(ServletInterceptor<?>[] interceptors, HttpServletBiConsumer target){
087
088        //builds the chain backwards
089        HttpServletBiConsumer current = target;
090        for(int i = interceptors.length-1; i >= 0; i--){
091            ServletInterceptor<?> interceptor = interceptors[i];
092
093            HttpServletBiConsumer next = current;
094            current = (req, res) -> interceptor.handle(req, res, next);
095        }
096        return current;
097    }
098
099    //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}