1 /*
2  * Copyright 2008-2019 by Emeric Vernat
3  *
4  *     This file is part of Java Melody.
5  *
6  * Licensed under the Apache License, Version 2.0 (the "License");
7  * you may not use this file except in compliance with the License.
8  * You may obtain a copy of the License at
9  *
10  *     http://www.apache.org/licenses/LICENSE-2.0
11  *
12  * Unless required by applicable law or agreed to in writing, software
13  * distributed under the License is distributed on an "AS IS" BASIS,
14  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  * See the License for the specific language governing permissions and
16  * limitations under the License.
17  */

18 package net.bull.javamelody;
19
20 import java.lang.reflect.InvocationHandler;
21 import java.lang.reflect.InvocationTargetException;
22 import java.lang.reflect.Method;
23
24 import javax.servlet.AsyncContext;
25 import javax.servlet.RequestDispatcher;
26 import javax.servlet.http.HttpServletRequest;
27 import javax.servlet.http.HttpServletRequestWrapper;
28 import javax.servlet.http.HttpServletResponse;
29
30 import net.bull.javamelody.internal.common.Parameters;
31 import net.bull.javamelody.internal.model.Counter;
32
33 /**
34  * Wrapping de l'interface javax.servlet.RequestDispatcher pour avoir les temps moyens de rendu
35  * des pages JSP.
36  * @author Emeric Vernat
37  */

38 final class JspWrapper implements InvocationHandler {
39     private static final Counter JSP_COUNTER = new Counter(Counter.JSP_COUNTER_NAME, "jsp.png",
40             JdbcWrapper.SINGLETON.getSqlCounter());
41     private static final boolean COUNTER_HIDDEN = Parameters.isCounterHidden(JSP_COUNTER.getName());
42     private static final boolean DISABLED = Parameter.DISABLED.getValueAsBoolean();
43
44     private final String path;
45     private final RequestDispatcher requestDispatcher;
46
47     private static class HttpRequestWrapper extends HttpServletRequestWrapper {
48         /**
49          * Constructs a request object wrapping the given request.
50          * @param request HttpServletRequest
51          */

52         HttpRequestWrapper(HttpServletRequest request) {
53             super(request);
54         }
55
56         /** {@inheritDoc} */
57         @Override
58         public RequestDispatcher getRequestDispatcher(String path) {
59             final RequestDispatcher requestDispatcher = super.getRequestDispatcher(path);
60             if (requestDispatcher == null) {
61                 return null;
62             }
63             // il n'est pas dit que path soit non null
64             final InvocationHandler invocationHandler = new JspWrapper(String.valueOf(path),
65                     requestDispatcher);
66             return JdbcWrapper.createProxy(requestDispatcher, invocationHandler);
67         }
68     }
69
70     private static class HttpRequestWrapper3 extends HttpRequestWrapper {
71         private final HttpServletResponse response;
72
73         /**
74          * Constructs a request object wrapping the given request.
75          * @param request HttpServletRequest
76          * @param response HttpServletResponse
77          */

78         HttpRequestWrapper3(HttpServletRequest request, HttpServletResponse response) {
79             super(request);
80             this.response = response;
81         }
82
83         @Override
84         public AsyncContext startAsync() {
85             // issue 217: after MonitoringFilter.doFilter, response is instance of CounterServletResponseWrapper,
86             // and if response.getWriter() has been called before calling request.startAsync(),
87             // then asyncContext.getResponse() should return the instance of CounterServletResponseWrapper
88             // and not the initial response without the wrapper,
89             // otherwise asyncContext.getResponse().getWriter() will throw something like
90             // "IllegalStateException: getOutputStream() has already been called for this response"
91             return super.startAsync(this, response);
92         }
93     }
94
95     /**
96      * Constructeur.
97      * @param path String
98      * @param requestDispatcher RequestDispatcher
99      */

100     JspWrapper(String path, RequestDispatcher requestDispatcher) {
101         super();
102         assert path != null;
103         assert requestDispatcher != null;
104         // quand ce RequestDispatcher est utilisé, le compteur est affiché
105         // sauf si le paramètre displayed-counters dit le contraire
106         JSP_COUNTER.setDisplayed(!COUNTER_HIDDEN);
107         JSP_COUNTER.setUsed(true);
108         this.path = path;
109         this.requestDispatcher = requestDispatcher;
110     }
111
112     static HttpServletRequest createHttpRequestWrapper(HttpServletRequest request,
113             HttpServletResponse response) {
114         if (DISABLED || COUNTER_HIDDEN) {
115             return request;
116         }
117         if (Parameters.getServletContext().getMajorVersion() >= 3) {
118             return new HttpRequestWrapper3(request, response);
119         }
120         return new HttpRequestWrapper(request);
121     }
122
123     static Counter getJspCounter() {
124         return JSP_COUNTER;
125     }
126
127     /**
128      * Intercepte une exécution de méthode sur une façade.
129      * @param proxy Object
130      * @param method Method
131      * @param args Object[]
132      * @return Object
133      * @throws Throwable t
134      */

135     @Override
136     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
137         final String methodName = method.getName();
138         // != for perf (strings interned: != is ok)
139         if ("include" != methodName && "forward" != methodName) { // NOPMD
140             return method.invoke(requestDispatcher, args);
141         }
142         boolean systemError = false;
143         try {
144             final String pathWithoutParameters;
145             final int indexOf = path.indexOf('?');
146             if (indexOf != -1) {
147                 pathWithoutParameters = path.substring(0, indexOf);
148             } else {
149                 pathWithoutParameters = path;
150             }
151             JSP_COUNTER.bindContextIncludingCpu(pathWithoutParameters);
152             return method.invoke(requestDispatcher, args);
153         } catch (final InvocationTargetException e) {
154             if (e.getCause() instanceof Error) {
155                 // on catche Error pour avoir les erreurs systèmes
156                 // mais pas Exception qui sont fonctionnelles en général
157                 systemError = true;
158             }
159             throw e;
160         } finally {
161             // on enregistre la requête dans les statistiques
162             JSP_COUNTER.addRequestForCurrentContext(systemError);
163         }
164     }
165 }
166