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