/*
* Copyright 2008-2017 by Emeric Vernat
*
* This file is part of Java Melody.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.bull.javamelody; // NOPMD
import static net.bull.javamelody.HttpParameters.CONTENT_DISPOSITION;
import static net.bull.javamelody.HttpParameters.COUNTER_PARAMETER;
import static net.bull.javamelody.HttpParameters.COUNTER_SUMMARY_PER_CLASS_PART;
import static net.bull.javamelody.HttpParameters.CURRENT_REQUESTS_PART;
import static net.bull.javamelody.HttpParameters.DATABASE_PART;
import static net.bull.javamelody.HttpParameters.GRAPH_PARAMETER;
import static net.bull.javamelody.HttpParameters.GRAPH_PART;
import static net.bull.javamelody.HttpParameters.HEAP_HISTO_PART;
import static net.bull.javamelody.HttpParameters.HOTSPOTS_PART;
import static net.bull.javamelody.HttpParameters.JNDI_PART;
import static net.bull.javamelody.HttpParameters.MBEANS_PART;
import static net.bull.javamelody.HttpParameters.PART_PARAMETER;
import static net.bull.javamelody.HttpParameters.PATH_PARAMETER;
import static net.bull.javamelody.HttpParameters.PROCESSES_PART;
import static net.bull.javamelody.HttpParameters.REQUEST_PARAMETER;
import static net.bull.javamelody.HttpParameters.RUNTIME_DEPENDENCIES_PART;
import static net.bull.javamelody.HttpParameters.SESSIONS_PART;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import net.bull.javamelody.SamplingProfiler.SampledMethod;
/**
* Contrôleur au sens MVC de l'ihm de monitoring pour la partie pdf.
* @author Emeric Vernat
*/
class PdfController {
private final HttpCookieManager httpCookieManager = new HttpCookieManager();
private final Collector collector;
private final CollectorServer collectorServer;
PdfController(Collector collector, CollectorServer collectorServer) {
super();
assert collector != null;
this.collector = collector;
this.collectorServer = collectorServer;
}
void doPdf(HttpServletRequest httpRequest, HttpServletResponse httpResponse,
List<JavaInformations> javaInformationsList) throws IOException {
addPdfContentTypeAndDisposition(httpRequest, httpResponse);
try {
final String part = httpRequest.getParameter(PART_PARAMETER);
if (part == null) {
if (!isFromCollectorServer() && !collector.isStopped()) {
// avant de faire l'affichage on fait une collecte, pour que les courbes
// et les compteurs par jour soit à jour avec les dernières requêtes
// sauf si c'est un serveur de collecte
// ou si la page de monitoring d'une webapp monitorée par un serveur de collecte est appelée par erreur
collector.collectLocalContextWithoutErrors();
}
final Range range = httpCookieManager.getRange(httpRequest, httpResponse);
final PdfReport pdfReport = new PdfReport(collector, isFromCollectorServer(),
javaInformationsList, range, httpResponse.getOutputStream());
pdfReport.toPdf();
} else {
try {
doPdfPart(httpRequest, httpResponse, part, javaInformationsList);
} catch (final IOException e) { // NOPMD
throw e;
} catch (final Exception e) {
// ne devrait pas arriver puisque les pdf ne s'affichent normalement pas sans afficher auparavant le html
throw new IllegalStateException(e);
}
}
} finally {
httpResponse.getOutputStream().flush();
}
}
private void doPdfPart(HttpServletRequest httpRequest, HttpServletResponse httpResponse,
String part, List<JavaInformations> javaInformationsList) throws Exception { // NOPMD
final PdfOtherReport pdfOtherReport = new PdfOtherReport(getApplication(),
httpResponse.getOutputStream());
final boolean done = doPdfPartForSystemActions(httpRequest, part, pdfOtherReport);
if (done) {
return;
}
if (CURRENT_REQUESTS_PART.equalsIgnoreCase(part)) {
final Range range = httpCookieManager.getRange(httpRequest, httpResponse);
doCurrentRequests(pdfOtherReport, javaInformationsList, range);
} else if (RUNTIME_DEPENDENCIES_PART.equalsIgnoreCase(part)) {
final Range range = httpCookieManager.getRange(httpRequest, httpResponse);
final String counterName = httpRequest.getParameter(COUNTER_PARAMETER);
final Counter counter = collector.getRangeCounter(range, counterName);
pdfOtherReport.writeRuntimeDependencies(counter, range);
} else if (COUNTER_SUMMARY_PER_CLASS_PART.equalsIgnoreCase(part)) {
final String requestId = httpRequest.getParameter(GRAPH_PARAMETER);
final Range range = httpCookieManager.getRange(httpRequest, httpResponse);
final String counterName = httpRequest.getParameter(COUNTER_PARAMETER);
final Counter counter = collector.getRangeCounter(range, counterName);
pdfOtherReport.writeCounterSummaryPerClass(collector, counter, requestId, range);
} else if (GRAPH_PART.equalsIgnoreCase(part)) {
final String requestId = httpRequest.getParameter(GRAPH_PARAMETER);
final Range range = httpCookieManager.getRange(httpRequest, httpResponse);
pdfOtherReport.writeRequestAndGraphDetail(collector, collectorServer, range, requestId);
} else {
throw new IllegalArgumentException(part);
}
}
private boolean doPdfPartForSystemActions(HttpServletRequest httpRequest, String part,
PdfOtherReport pdfOtherReport) throws Exception { // NOPMD
if (SESSIONS_PART.equalsIgnoreCase(part)) {
doSessions(pdfOtherReport);
} else if (HOTSPOTS_PART.equalsIgnoreCase(part)) {
doHotspots(pdfOtherReport);
} else if (PROCESSES_PART.equalsIgnoreCase(part)) {
doProcesses(pdfOtherReport);
} else if (DATABASE_PART.equalsIgnoreCase(part)) {
final int index = DatabaseInformations
.parseRequestIndex(httpRequest.getParameter(REQUEST_PARAMETER));
doDatabase(pdfOtherReport, index);
} else if (JNDI_PART.equalsIgnoreCase(part)) {
doJndi(pdfOtherReport, httpRequest.getParameter(PATH_PARAMETER));
} else if (MBEANS_PART.equalsIgnoreCase(part)) {
doMBeans(pdfOtherReport);
} else if (HEAP_HISTO_PART.equalsIgnoreCase(part)) {
doHeapHisto(pdfOtherReport);
} else {
// pas pour nous
return false;
}
// fait et ok
return true;
}
private void doCurrentRequests(PdfOtherReport pdfOtherReport,
List<JavaInformations> javaInformationsList, final Range range) throws IOException {
final List<Counter> counters = collector.getRangeCountersToBeDisplayed(range);
final Map<JavaInformations, List<CounterRequestContext>> currentRequests;
if (!isFromCollectorServer()) {
// on est dans l'application monitorée
assert collectorServer == null;
assert javaInformationsList.size() == 1;
final JavaInformations javaInformations = javaInformationsList.get(0);
final List<CounterRequestContext> rootCurrentContexts = collector
.getRootCurrentContexts(counters);
currentRequests = Collections.singletonMap(javaInformations, rootCurrentContexts);
} else {
currentRequests = collectorServer.collectCurrentRequests(collector.getApplication());
}
final long timeOfSnapshot = System.currentTimeMillis();
pdfOtherReport.writeAllCurrentRequestsAsPart(currentRequests, collector, counters,
timeOfSnapshot);
}
private void doSessions(PdfOtherReport pdfOtherReport) throws IOException {
// par sécurité
Action.checkSystemActionsEnabled();
final List<SessionInformations> sessionsInformations;
if (!isFromCollectorServer()) {
sessionsInformations = SessionListener.getAllSessionsInformations();
} else {
sessionsInformations = collectorServer.collectSessionInformations(getApplication(),
null);
}
pdfOtherReport.writeSessionInformations(sessionsInformations);
}
private void doHotspots(PdfOtherReport pdfOtherReport) throws IOException {
// par sécurité
Action.checkSystemActionsEnabled();
if (!isFromCollectorServer()) {
final List<SampledMethod> hotspots = collector.getHotspots();
pdfOtherReport.writeHotspots(hotspots);
} else {
final List<SampledMethod> hotspots = collectorServer.collectHotspots(getApplication());
pdfOtherReport.writeHotspots(hotspots);
}
}
private void doProcesses(PdfOtherReport pdfOtherReport) throws IOException {
// par sécurité
Action.checkSystemActionsEnabled();
if (!isFromCollectorServer()) {
final List<ProcessInformations> processInformations = ProcessInformations
.buildProcessInformations();
pdfOtherReport.writeProcessInformations(processInformations);
} else {
final Map<String, List<ProcessInformations>> processesByTitle = collectorServer
.collectProcessInformations(getApplication());
pdfOtherReport.writeProcessInformations(processesByTitle);
}
}
private void doDatabase(PdfOtherReport pdfOtherReport, final int index) throws Exception { // NOPMD
// par sécurité
Action.checkSystemActionsEnabled();
final DatabaseInformations databaseInformations;
if (!isFromCollectorServer()) {
databaseInformations = new DatabaseInformations(index);
} else {
databaseInformations = collectorServer.collectDatabaseInformations(getApplication(),
index);
}
pdfOtherReport.writeDatabaseInformations(databaseInformations);
}
private void doJndi(PdfOtherReport pdfOtherReport, String path) throws Exception { // NOPMD
// par sécurité
Action.checkSystemActionsEnabled();
final List<JndiBinding> jndiBindings;
if (!isFromCollectorServer()) {
jndiBindings = JndiBinding.listBindings(path);
} else {
jndiBindings = collectorServer.collectJndiBindings(getApplication(), path);
}
pdfOtherReport.writeJndi(jndiBindings, JndiBinding.normalizePath(path));
}
private void doMBeans(PdfOtherReport pdfOtherReport) throws Exception { // NOPMD
// par sécurité
Action.checkSystemActionsEnabled();
if (!isFromCollectorServer()) {
final List<MBeanNode> nodes = MBeans.getAllMBeanNodes();
pdfOtherReport.writeMBeans(nodes);
} else {
final Map<String, List<MBeanNode>> allMBeans = collectorServer
.collectMBeans(getApplication());
pdfOtherReport.writeMBeans(allMBeans);
}
}
private void doHeapHisto(PdfOtherReport pdfOtherReport) throws Exception { // NOPMD
// par sécurité
Action.checkSystemActionsEnabled();
final HeapHistogram heapHistogram;
if (!isFromCollectorServer()) {
heapHistogram = VirtualMachine.createHeapHistogram();
} else {
heapHistogram = collectorServer.collectHeapHistogram(getApplication());
}
pdfOtherReport.writeHeapHistogram(heapHistogram);
}
void addPdfContentTypeAndDisposition(HttpServletRequest httpRequest,
HttpServletResponse httpResponse) {
httpResponse.setContentType("application/pdf");
final String contentDisposition = encodeFileNameToContentDisposition(httpRequest,
PdfReport.getFileName(getApplication()));
// encoding des CRLF pour http://en.wikipedia.org/wiki/HTTP_response_splitting
httpResponse.addHeader(CONTENT_DISPOSITION,
contentDisposition.replace('\n', '_').replace('\r', '_'));
}
private String getApplication() {
return collector.getApplication();
}
private boolean isFromCollectorServer() {
return collectorServer != null;
}
/**
* Encode un nom de fichier avec des % pour Content-Disposition, avec téléchargement.
* (US-ASCII + Encode-Word : http://www.ietf.org/rfc/rfc2183.txt, http://www.ietf.org/rfc/rfc2231.txt
* sauf en MS IE qui ne supporte pas cet encodage et qui n'en a pas besoin)
* @param httpRequest HttpServletRequest
* @param fileName String
* @return String
*/
private static String encodeFileNameToContentDisposition(HttpServletRequest httpRequest,
String fileName) {
assert fileName != null;
final String userAgent = httpRequest.getHeader("user-agent");
if (userAgent != null && userAgent.contains("MSIE")) {
return "attachment;filename=" + fileName;
}
return encodeFileNameToStandardContentDisposition(fileName);
}
private static String encodeFileNameToStandardContentDisposition(String fileName) {
final int length = fileName.length();
final StringBuilder sb = new StringBuilder(length + length / 4);
// attachment et non inline pour proposer l'enregistrement (sauf IE6)
// et non l'affichage direct dans le navigateur
sb.append("attachment;filename=\"");
char c;
for (int i = 0; i < length; i++) {
c = fileName.charAt(i);
if (isEncodingNotNeeded(c)) {
sb.append(c);
} else {
sb.append('%');
if (c < 16) {
sb.append('0');
}
sb.append(Integer.toHexString(c));
}
}
sb.append('"');
return sb.toString();
}
private static boolean isEncodingNotNeeded(char c) {
return c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z' || c >= '0' && c <= '9' || c == '.'
|| c == '_';
}
}