1
18 package net.bull.javamelody.internal.web;
19
20 import java.io.BufferedWriter;
21 import java.io.File;
22 import java.io.FileWriter;
23 import java.io.IOException;
24 import java.io.OutputStreamWriter;
25 import java.nio.charset.StandardCharsets;
26 import java.util.Collections;
27 import java.util.List;
28 import java.util.Map;
29
30 import javax.servlet.http.HttpServletRequest;
31 import javax.servlet.http.HttpServletResponse;
32
33 import net.bull.javamelody.JdbcWrapper;
34 import net.bull.javamelody.Parameter;
35 import net.bull.javamelody.SessionListener;
36 import net.bull.javamelody.internal.common.HttpParameter;
37 import net.bull.javamelody.internal.common.HttpPart;
38 import net.bull.javamelody.internal.common.I18N;
39 import net.bull.javamelody.internal.common.LOG;
40 import net.bull.javamelody.internal.common.Parameters;
41 import net.bull.javamelody.internal.model.Action;
42 import net.bull.javamelody.internal.model.CacheInformations;
43 import net.bull.javamelody.internal.model.Collector;
44 import net.bull.javamelody.internal.model.CollectorServer;
45 import net.bull.javamelody.internal.model.DatabaseInformations;
46 import net.bull.javamelody.internal.model.HeapHistogram;
47 import net.bull.javamelody.internal.model.JCacheInformations;
48 import net.bull.javamelody.internal.model.JavaInformations;
49 import net.bull.javamelody.internal.model.JndiBinding;
50 import net.bull.javamelody.internal.model.MBeanNode;
51 import net.bull.javamelody.internal.model.MBeans;
52 import net.bull.javamelody.internal.model.Period;
53 import net.bull.javamelody.internal.model.ProcessInformations;
54 import net.bull.javamelody.internal.model.Range;
55 import net.bull.javamelody.internal.model.SamplingProfiler.SampledMethod;
56 import net.bull.javamelody.internal.model.SessionInformations;
57 import net.bull.javamelody.internal.model.VirtualMachine;
58 import net.bull.javamelody.internal.web.RequestToMethodMapper.RequestParameter;
59 import net.bull.javamelody.internal.web.RequestToMethodMapper.RequestPart;
60 import net.bull.javamelody.internal.web.html.HtmlReport;
61
62
66 public class HtmlController {
67 static final String HTML_BODY_FORMAT = "htmlbody";
68 private static final boolean CONTENT_SECURITY_POLICY_ENABLED = Parameter.CONTENT_SECURITY_POLICY_ENABLED
69 .getValue() == null || Parameter.CONTENT_SECURITY_POLICY_ENABLED.getValueAsBoolean();
70 private static final String X_FRAME_OPTIONS = Parameter.X_FRAME_OPTIONS.getValue();
71 private static final RequestToMethodMapper<HtmlController> REQUEST_TO_METHOD_MAPPER = new RequestToMethodMapper<>(
72 HtmlController.class);
73 private final HttpCookieManager httpCookieManager = new HttpCookieManager();
74 private final Collector collector;
75 private final CollectorServer collectorServer;
76 private final String messageForReport;
77 private final String anchorNameForRedirect;
78 private HtmlReport htmlReport;
79
80 HtmlController(Collector collector, CollectorServer collectorServer, String messageForReport,
81 String anchorNameForRedirect) {
82 super();
83 assert collector != null;
84 this.collector = collector;
85 this.collectorServer = collectorServer;
86 this.messageForReport = messageForReport;
87 this.anchorNameForRedirect = anchorNameForRedirect;
88 }
89
90 void doHtml(HttpServletRequest httpRequest, HttpServletResponse httpResponse,
91 List<JavaInformations> javaInformationsList) throws IOException {
92 final String part = HttpParameter.PART.getParameterFrom(httpRequest);
93 if (!isFromCollectorServer() && isLocalCollectNeeded(part) && !collector.isStopped()) {
94
95
96
97
98 collector.collectLocalContextWithoutErrors();
99 }
100
101
102 try (BufferedWriter writer = getWriter(httpResponse)) {
103 final Range range = httpCookieManager.getRange(httpRequest, httpResponse);
104 this.htmlReport = new HtmlReport(collector, collectorServer, javaInformationsList,
105 range, writer);
106 if (part == null) {
107 htmlReport.toHtml(messageForReport, anchorNameForRedirect);
108 } else if (HttpPart.THREADS_DUMP.isPart(httpRequest)) {
109 httpResponse.setContentType("text/plain; charset=UTF-8");
110 htmlReport.writeThreadsDump();
111 } else {
112 REQUEST_TO_METHOD_MAPPER.invoke(httpRequest, this);
113 }
114 }
115 }
116
117 static boolean isLocalCollectNeeded(String part) {
118 return part == null || HttpPart.CURRENT_REQUESTS.getName().equals(part)
119 || HttpPart.GRAPH.getName().equals(part)
120 || HttpPart.COUNTER_SUMMARY_PER_CLASS.getName().equals(part);
121 }
122
123 public static BufferedWriter getWriter(HttpServletResponse httpResponse) throws IOException {
124 httpResponse.setContentType("text/html; charset=UTF-8");
125 if (CONTENT_SECURITY_POLICY_ENABLED) {
126 final String analyticsId = Parameter.ANALYTICS_ID.getValue();
127 final boolean analyticsEnabled = analyticsId != null && !"disabled".equals(analyticsId);
128 httpResponse.setHeader("Content-Security-Policy",
129 "default-src 'self'"
130 + (analyticsEnabled ? " https: : "")
131 + "; object-src 'none';");
132 }
133 if (X_FRAME_OPTIONS == null) {
134
135 httpResponse.setHeader("X-Frame-Options", "SAMEORIGIN");
136 } else if (!"ALLOWALL".equals(X_FRAME_OPTIONS)) {
137 httpResponse.setHeader("X-Frame-Options", X_FRAME_OPTIONS);
138 }
139 try {
140 return new BufferedWriter(
141 new OutputStreamWriter(httpResponse.getOutputStream(), StandardCharsets.UTF_8));
142 } catch (final IllegalStateException e) {
143
144 return new BufferedWriter(httpResponse.getWriter());
145 }
146 }
147
148 @RequestPart(HttpPart.GRAPH)
149 void doRequestGraphAndDetail(@RequestParameter(HttpParameter.GRAPH) String graphName)
150 throws IOException {
151 htmlReport.writeRequestAndGraphDetail(graphName);
152 }
153
154 @RequestPart(HttpPart.USAGES)
155 void doRequestUsages(@RequestParameter(HttpParameter.GRAPH) String graphName)
156 throws IOException {
157 htmlReport.writeRequestUsages(graphName);
158 }
159
160 @RequestPart(HttpPart.CURRENT_REQUESTS)
161 void doAllCurrentRequestsAsPart() throws IOException {
162 htmlReport.writeAllCurrentRequestsAsPart();
163 }
164
165 @RequestPart(HttpPart.THREADS)
166 void doAllThreadsAsPart() throws IOException {
167 htmlReport.writeAllThreadsAsPart();
168 }
169
170 @RequestPart(HttpPart.COUNTER_SUMMARY_PER_CLASS)
171 void doCounterSummaryPerClass(@RequestParameter(HttpParameter.COUNTER) String counterName,
172 @RequestParameter(HttpParameter.GRAPH) String requestId) throws IOException {
173 htmlReport.writeCounterSummaryPerClass(counterName, requestId);
174 }
175
176 @RequestPart(HttpPart.SOURCE)
177 void doSource(@RequestParameter(HttpParameter.CLASS) String className) throws IOException {
178 htmlReport.writeSource(className);
179 }
180
181 @RequestPart(HttpPart.DEPENDENCIES)
182 void doDependencies() throws IOException {
183 htmlReport.writeDependencies();
184 }
185
186 @RequestPart(HttpPart.SESSIONS)
187 void doSessions(@RequestParameter(HttpParameter.SESSION_ID) String sessionId)
188 throws IOException {
189
190 Action.checkSystemActionsEnabled();
191 final List<SessionInformations> sessionsInformations;
192 if (!isFromCollectorServer()) {
193 if (sessionId == null) {
194 sessionsInformations = SessionListener.getAllSessionsInformations();
195 } else {
196 sessionsInformations = Collections.singletonList(
197 SessionListener.getSessionInformationsBySessionId(sessionId));
198 }
199 } else {
200 sessionsInformations = collectorServer.collectSessionInformations(getApplication(),
201 sessionId);
202 }
203 if (sessionId == null || sessionsInformations.isEmpty()) {
204 htmlReport.writeSessions(sessionsInformations, messageForReport,
205 HttpPart.SESSIONS.getName());
206 } else {
207 final SessionInformations sessionInformation = sessionsInformations.get(0);
208 htmlReport.writeSessionDetail(sessionId, sessionInformation);
209 }
210 }
211
212 @RequestPart(HttpPart.HOTSPOTS)
213 void doHotspots() throws IOException {
214
215 Action.checkSystemActionsEnabled();
216 if (!isFromCollectorServer()) {
217 final List<SampledMethod> hotspots = collector.getHotspots();
218 htmlReport.writeHotspots(hotspots);
219 } else {
220 final List<SampledMethod> hotspots = collectorServer.collectHotspots(getApplication());
221 htmlReport.writeHotspots(hotspots);
222 }
223 }
224
225 @RequestPart(HttpPart.HEAP_HISTO)
226 void doHeapHisto() throws IOException {
227
228 Action.checkSystemActionsEnabled();
229 final HeapHistogram heapHistogram;
230 try {
231 if (!isFromCollectorServer()) {
232 heapHistogram = VirtualMachine.createHeapHistogram();
233 } else {
234 heapHistogram = collectorServer.collectHeapHistogram(getApplication());
235 }
236 } catch (final Exception e) {
237 LOG.warn("heaphisto report failed", e);
238 htmlReport.writeMessageIfNotNull(String.valueOf(e.getMessage()), null);
239 return;
240 }
241 htmlReport.writeHeapHistogram(heapHistogram, messageForReport,
242 HttpPart.HEAP_HISTO.getName());
243 }
244
245 @RequestPart(HttpPart.PROCESSES)
246 void doProcesses() throws IOException {
247
248 Action.checkSystemActionsEnabled();
249 try {
250 if (!isFromCollectorServer()) {
251 final List<ProcessInformations> processInformationsList = ProcessInformations
252 .buildProcessInformations();
253 htmlReport.writeProcesses(processInformationsList);
254 } else {
255 final Map<String, List<ProcessInformations>> processInformationsByTitle = collectorServer
256 .collectProcessInformations(getApplication());
257 htmlReport.writeProcesses(processInformationsByTitle);
258 }
259 } catch (final Exception e) {
260 LOG.warn("processes report failed", e);
261 htmlReport.writeMessageIfNotNull(String.valueOf(e.getMessage()), null);
262 }
263 }
264
265 @RequestPart(HttpPart.DATABASE)
266 void doDatabase(@RequestParameter(HttpParameter.REQUEST) String requestIndex)
267 throws IOException {
268
269 Action.checkSystemActionsEnabled();
270 try {
271 final int index = DatabaseInformations.parseRequestIndex(requestIndex);
272 final DatabaseInformations databaseInformations;
273 if (!isFromCollectorServer()) {
274 databaseInformations = new DatabaseInformations(index);
275 } else {
276 databaseInformations = collectorServer.collectDatabaseInformations(getApplication(),
277 index);
278 }
279 htmlReport.writeDatabase(databaseInformations);
280 } catch (final Exception e) {
281 LOG.warn("database report failed", e);
282 htmlReport.writeMessageIfNotNull(String.valueOf(e.getMessage()), null);
283 }
284 }
285
286 @RequestPart(HttpPart.CONNECTIONS)
287 void doConnections(@RequestParameter(HttpParameter.FORMAT) String format) throws IOException {
288 assert !isFromCollectorServer();
289
290 Action.checkSystemActionsEnabled();
291 final boolean withoutHeaders = HTML_BODY_FORMAT.equalsIgnoreCase(format);
292 htmlReport.writeConnections(JdbcWrapper.getConnectionInformationsList(), withoutHeaders);
293 }
294
295 @RequestPart(HttpPart.JNDI)
296 void doJndi(@RequestParameter(HttpParameter.PATH) String path) throws IOException {
297
298 Action.checkSystemActionsEnabled();
299 try {
300 final List<JndiBinding> jndiBindings;
301 if (!isFromCollectorServer()) {
302 jndiBindings = JndiBinding.listBindings(path);
303 } else {
304 jndiBindings = collectorServer.collectJndiBindings(getApplication(), path);
305 }
306 htmlReport.writeJndi(jndiBindings, path);
307 } catch (final Exception e) {
308 LOG.warn("jndi report failed", e);
309 htmlReport.writeMessageIfNotNull(String.valueOf(e.getMessage()), null);
310 }
311 }
312
313 @RequestPart(HttpPart.MBEANS)
314 void doMBeans() throws IOException {
315
316 Action.checkSystemActionsEnabled();
317 try {
318 if (!isFromCollectorServer()) {
319 final List<MBeanNode> nodes = MBeans.getAllMBeanNodes();
320 htmlReport.writeMBeans(nodes);
321 } else {
322 final Map<String, List<MBeanNode>> allMBeans = collectorServer
323 .collectMBeans(getApplication());
324 htmlReport.writeMBeans(allMBeans);
325 }
326 } catch (final Exception e) {
327 LOG.warn("mbeans report failed", e);
328 htmlReport.writeMessageIfNotNull(String.valueOf(e.getMessage()), null);
329 }
330 }
331
332 @RequestPart(HttpPart.CRASHES)
333 void doCrashes() throws IOException {
334 Action.checkSystemActionsEnabled();
335 htmlReport.writeCrashes();
336 }
337
338 @RequestPart(HttpPart.SPRING_BEANS)
339 void doSpringBeans() throws IOException {
340 htmlReport.writeSpringContext();
341 }
342
343 @RequestPart(HttpPart.CACHE_KEYS)
344 void doCacheKeys(@RequestParameter(HttpParameter.CACHE_ID) String cacheId,
345 @RequestParameter(HttpParameter.FORMAT) String format) throws IOException {
346 assert !isFromCollectorServer();
347 final CacheInformations cacheInfo = CacheInformations
348 .buildCacheInformationsWithKeys(cacheId);
349 final boolean withoutHeaders = HTML_BODY_FORMAT.equalsIgnoreCase(format);
350 final String cacheKeysPart = HttpPart.CACHE_KEYS.toString() + '&' + HttpParameter.CACHE_ID
351 + '=' + I18N.urlEncode(cacheId);
352 htmlReport.writeCacheWithKeys(cacheId, cacheInfo, messageForReport, cacheKeysPart,
353 withoutHeaders);
354 }
355
356 @RequestPart(HttpPart.JCACHE_KEYS)
357 void doJCacheKeys(@RequestParameter(HttpParameter.CACHE_ID) String cacheId,
358 @RequestParameter(HttpParameter.FORMAT) String format) throws IOException {
359 assert !isFromCollectorServer();
360 final JCacheInformations cacheInfo = JCacheInformations
361 .buildJCacheInformationsWithKeys(cacheId);
362 final boolean withoutHeaders = HTML_BODY_FORMAT.equalsIgnoreCase(format);
363 final String jcacheKeysPart = HttpPart.JCACHE_KEYS.toString() + '&' + HttpParameter.CACHE_ID
364 + '=' + I18N.urlEncode(cacheId);
365 htmlReport.writeJCacheWithKeys(cacheId, cacheInfo, messageForReport, jcacheKeysPart,
366 withoutHeaders);
367 }
368
369 @RequestPart(HttpPart.HASH_PASSWORD)
370 void doHashPassword(@RequestParameter(HttpParameter.ALGORITHM) String algorithm,
371 @RequestParameter(HttpParameter.REQUEST) String password) throws IOException {
372 htmlReport.writeHashPassword(algorithm, password);
373 }
374
375 void writeHtmlToLastShutdownFile() {
376 try {
377 final File dir = Parameters.getStorageDirectory(getApplication());
378 if (!dir.mkdirs() && !dir.exists()) {
379 throw new IOException("JavaMelody directory can't be created: " + dir.getPath());
380 }
381 final File lastShutdownFile = new File(dir, "last_shutdown.html");
382 try (BufferedWriter writer = new BufferedWriter(new FileWriter(lastShutdownFile))) {
383 final JavaInformations javaInformations = new JavaInformations(
384 Parameters.getServletContext(), true);
385
386 final HtmlReport myHtmlReport = new HtmlReport(collector, collectorServer,
387 Collections.singletonList(javaInformations), Period.JOUR, writer);
388 myHtmlReport.writeLastShutdown();
389 }
390 } catch (final IOException e) {
391 LOG.warn("exception while writing the last shutdown report", e);
392 }
393 }
394
395 private String getApplication() {
396 return collector.getApplication();
397 }
398
399 private boolean isFromCollectorServer() {
400 return collectorServer != null;
401 }
402 }
403