net.bull.javamelody.internal.common.Parameters Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of javamelody-core Show documentation
Show all versions of javamelody-core Show documentation
Monitoring of JavaEE applications
/*
* Copyright 2008-2019 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.internal.common;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetAddress;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.TreeMap;
import javax.servlet.FilterConfig;
import javax.servlet.ServletContext;
import net.bull.javamelody.Parameter;
import net.bull.javamelody.internal.model.TransportFormat;
/**
* Classe d'accès aux paramètres du monitoring.
* @author Emeric Vernat
*/
public final class Parameters {
public static final String PARAMETER_SYSTEM_PREFIX = "javamelody.";
public static final File TEMPORARY_DIRECTORY = new File(System.getProperty("java.io.tmpdir"));
public static final String JAVA_VERSION = System.getProperty("java.version");
public static final String JAVAMELODY_VERSION = getJavaMelodyVersion();
// default monitoring-path is "/monitoring" in the http URL
private static final String DEFAULT_MONITORING_PATH = "/monitoring";
// résolution (ou pas) par défaut en s de stockage des valeurs dans les fichiers RRD
private static final int DEFAULT_RESOLUTION_SECONDS = 60;
// stockage des fichiers RRD de JRobin dans le répertoire temp/javamelody/ par défaut
private static final String DEFAULT_DIRECTORY = "javamelody";
// nom du fichier stockant les applications et leurs urls dans le répertoire de stockage
private static final String COLLECTOR_APPLICATIONS_FILENAME = "applications.properties";
private static final boolean PDF_ENABLED = computePdfEnabled();
private static Map> urlsByApplications;
private static Map> applicationsByAggregationApplications;
private static FilterConfig filterConfig;
private static ServletContext servletContext;
private static String lastConnectUrl;
private static Properties lastConnectInfo;
private static boolean dnsLookupsDisabled;
private Parameters() {
super();
}
public static void initialize(FilterConfig config) {
filterConfig = config;
if (config != null) {
final ServletContext context = config.getServletContext();
initialize(context);
}
}
public static void initialize(ServletContext context) {
if ("1.6".compareTo(JAVA_VERSION) > 0) {
throw new IllegalStateException(
"La version java doit être 1.6 au minimum et non " + JAVA_VERSION);
}
servletContext = context;
dnsLookupsDisabled = Parameter.DNS_LOOKUPS_DISABLED.getValueAsBoolean();
}
public static void initJdbcDriverParameters(String connectUrl, Properties connectInfo) {
lastConnectUrl = connectUrl;
lastConnectInfo = connectInfo;
}
/**
* @return Contexte de servlet de la webapp, soit celle monitorée ou soit celle de collecte.
*/
public static ServletContext getServletContext() {
assert servletContext != null;
return servletContext;
}
public static String getLastConnectUrl() {
return lastConnectUrl;
}
public static Properties getLastConnectInfo() {
return lastConnectInfo;
}
/**
* @return Nom et urls des applications telles que paramétrées dans un serveur de collecte.
* @throws IOException e
*/
public static Map> getCollectorUrlsByApplications() throws IOException {
if (urlsByApplications == null) {
readCollectorApplications();
}
return Collections.unmodifiableMap(urlsByApplications);
}
public static Map> getApplicationsByAggregationApplication()
throws IOException {
if (applicationsByAggregationApplications == null) {
readCollectorApplications();
}
return Collections.unmodifiableMap(applicationsByAggregationApplications);
}
public static void addCollectorApplication(String application, List urls)
throws IOException {
assert application != null;
assert urls != null && !urls.isEmpty();
// initialisation si besoin
getCollectorUrlsByApplications();
urlsByApplications.put(application, urls);
writeCollectorApplications();
}
public static void addCollectorAggregationApplication(String aggregationApplication,
List aggregatedApplications) throws IOException {
assert aggregationApplication != null;
assert aggregatedApplications != null && !aggregatedApplications.isEmpty();
// initialisation si besoin
getCollectorUrlsByApplications();
applicationsByAggregationApplications.put(aggregationApplication, aggregatedApplications);
writeCollectorApplications();
}
public static void removeCollectorApplication(String application) throws IOException {
assert application != null;
// initialisation si besoin
getCollectorUrlsByApplications();
if (urlsByApplications.containsKey(application)) {
urlsByApplications.remove(application);
} else {
applicationsByAggregationApplications.remove(application);
}
synchronizeAggregationApplications();
writeCollectorApplications();
}
private static void writeCollectorApplications() throws IOException {
final Properties properties = new Properties();
final String monitoringPath = getMonitoringPath();
for (final Map.Entry> entry : urlsByApplications.entrySet()) {
final List urls = entry.getValue();
assert urls != null && !urls.isEmpty();
final StringBuilder sb = new StringBuilder();
for (final URL url : urls) {
final String urlString = url.toString();
// on enlève le suffixe ajouté précédemment dans parseUrl
final String webappUrl = urlString.substring(0,
urlString.lastIndexOf(monitoringPath));
if (webappUrl.indexOf(',') != -1) {
throw new IOException("The URL should not contain a comma.");
}
sb.append(webappUrl).append(',');
}
sb.delete(sb.length() - 1, sb.length());
properties.put(entry.getKey(), sb.toString());
}
for (final Map.Entry> entry : applicationsByAggregationApplications
.entrySet()) {
final List applications = entry.getValue();
final StringBuilder sb = new StringBuilder();
for (final String application : applications) {
if (application.indexOf(',') != -1) {
throw new IOException("The application name should not contain a comma.");
}
sb.append(application).append(',');
}
sb.delete(sb.length() - 1, sb.length());
properties.put(entry.getKey(), sb.toString());
}
final File collectorApplicationsFile = getCollectorApplicationsFile();
final File directory = collectorApplicationsFile.getParentFile();
if (!directory.mkdirs() && !directory.exists()) {
throw new IOException("JavaMelody directory can't be created: " + directory.getPath());
}
try (FileOutputStream output = new FileOutputStream(collectorApplicationsFile)) {
properties.store(output, "urls of the applications to monitor");
}
}
private static void readCollectorApplications() throws IOException {
// le fichier applications.properties contient les noms et les urls des applications à monitorer
// par ex.: recette=http://recette1:8080/myapp
// ou recette2=http://recette2:8080/myapp
// ou production=http://prod1:8080/myapp,http://prod2:8080/myapp
// ou aggregation=recette,recette2
// Dans une instance de Properties, les propriétés ne sont pas ordonnées,
// mais elles seront ordonnées lorsqu'elles seront mises dans cette TreeMap
final Map> applications = new TreeMap<>();
final Map> aggregationApplications = new TreeMap<>();
final File file = getCollectorApplicationsFile();
if (file.exists()) {
final Properties properties = new Properties();
try (FileInputStream input = new FileInputStream(file)) {
properties.load(input);
}
@SuppressWarnings("unchecked")
final List propertyNames = (List) Collections
.list(properties.propertyNames());
for (final String property : propertyNames) {
final String value = String.valueOf(properties.get(property));
if (value.startsWith("http")) {
applications.put(property, parseUrls(value));
} else {
aggregationApplications.put(property,
new ArrayList<>(Arrays.asList(value.split(","))));
}
}
}
urlsByApplications = applications;
applicationsByAggregationApplications = aggregationApplications;
synchronizeAggregationApplications();
}
private static void synchronizeAggregationApplications() {
for (final Iterator> it1 = applicationsByAggregationApplications.values()
.iterator(); it1.hasNext();) {
final List aggregatedApplications = it1.next();
for (final Iterator it2 = aggregatedApplications.iterator(); it2.hasNext();) {
final String aggregatedApplication = it2.next();
if (!urlsByApplications.containsKey(aggregatedApplication)
&& !applicationsByAggregationApplications
.containsKey(aggregatedApplication)) {
// application aggrégée inconnue, on la supprime
it2.remove();
}
}
if (aggregatedApplications.isEmpty()) {
// application d'aggrégation vide, on la supprime
it1.remove();
}
}
}
public static File getCollectorApplicationsFile() {
return new File(getStorageDirectory(""), COLLECTOR_APPLICATIONS_FILENAME);
}
public static List parseUrls(String value) throws MalformedURLException {
// pour un cluster, le paramètre vaut "url1,url2"
final TransportFormat transportFormat;
if (Parameter.TRANSPORT_FORMAT.getValue() == null) {
transportFormat = TransportFormat.SERIALIZED;
} else {
transportFormat = TransportFormat
.valueOfIgnoreCase(Parameter.TRANSPORT_FORMAT.getValue());
}
final String suffix = getMonitoringPath() + "?collector=stop&format="
+ transportFormat.getCode();
final String[] urlsArray = value.split(",");
final List urls = new ArrayList<>(urlsArray.length);
for (final String s : urlsArray) {
String s2 = s.trim();
while (s2.endsWith("/")) {
s2 = s2.substring(0, s2.length() - 1);
}
final URL url = new URL(s2 + suffix);
urls.add(url);
}
return urls;
}
public static String getMonitoringPath() {
final String parameterValue = Parameter.MONITORING_PATH.getValue();
if (parameterValue == null) {
return DEFAULT_MONITORING_PATH;
}
return parameterValue;
}
/**
* @return nom réseau de la machine
*/
public static String getHostName() {
if (dnsLookupsDisabled) {
return "localhost";
}
try {
return InetAddress.getLocalHost().getHostName();
} catch (final UnknownHostException ex) {
return "unknown";
}
}
/**
* @return adresse ip de la machine
*/
public static String getHostAddress() {
if (dnsLookupsDisabled) {
return "127.0.0.1"; // NOPMD
}
try {
return InetAddress.getLocalHost().getHostAddress();
} catch (final UnknownHostException ex) {
return "unknown";
}
}
/**
* @param fileName Nom du fichier de resource.
* @return Chemin complet d'une resource.
*/
public static String getResourcePath(String fileName) {
return "/net/bull/javamelody/resource/" + fileName;
}
/**
* @return Résolution en secondes des courbes et période d'appels par le serveur de collecte le cas échéant.
*/
public static int getResolutionSeconds() {
final String param = Parameter.RESOLUTION_SECONDS.getValue();
if (param != null) {
// lance une NumberFormatException si ce n'est pas un nombre
final int result = Integer.parseInt(param);
if (result <= 0) {
throw new IllegalStateException(
"The parameter resolution-seconds should be > 0 (between 60 and 600 recommended)");
}
return result;
}
return DEFAULT_RESOLUTION_SECONDS;
}
/**
* @param application Nom de l'application
* @return Répertoire de stockage des compteurs et des données pour les courbes.
*/
public static File getStorageDirectory(String application) {
final String param = Parameter.STORAGE_DIRECTORY.getValue();
final String dir;
if (param == null) {
dir = DEFAULT_DIRECTORY;
} else {
dir = param;
}
// Si le nom du répertoire commence par '/' (ou "drive specifier" sur Windows),
// on considère que c'est un chemin absolu,
// sinon on considère que c'est un chemin relatif par rapport au répertoire temporaire
// ('temp' dans TOMCAT_HOME pour tomcat).
final String directory;
if (!dir.isEmpty() && new File(dir).isAbsolute()) {
directory = dir;
} else {
directory = TEMPORARY_DIRECTORY.getPath() + '/' + dir;
}
if (servletContext != null) {
return new File(directory + '/' + application);
}
return new File(directory);
}
/**
* Booléen selon que le paramètre no-database vaut true.
* @return boolean
*/
public static boolean isNoDatabase() {
return Parameter.NO_DATABASE.getValueAsBoolean();
}
/**
* Booléen selon que le paramètre system-actions-enabled vaut true.
* @return boolean
*/
public static boolean isSystemActionsEnabled() {
final String parameter = Parameter.SYSTEM_ACTIONS_ENABLED.getValue();
return parameter == null || Boolean.parseBoolean(parameter);
}
public static boolean isPdfEnabled() {
return PDF_ENABLED;
}
private static boolean computePdfEnabled() {
try {
Class.forName("com.lowagie.text.Document");
return true;
} catch (final ClassNotFoundException e) {
return false;
}
}
/**
* Retourne false si le paramètre displayed-counters n'a pas été défini
* ou si il contient le compteur dont le nom est paramètre,
* et retourne true sinon (c'est-à-dire si le paramètre displayed-counters est défini
* et si il ne contient pas le compteur dont le nom est paramètre).
* @param counterName Nom du compteur
* @return boolean
*/
public static boolean isCounterHidden(String counterName) {
final String displayedCounters = Parameter.DISPLAYED_COUNTERS.getValue();
if (displayedCounters == null) {
return false;
}
for (final String displayedCounter : displayedCounters.split(",")) {
final String displayedCounterName = displayedCounter.trim();
if (counterName.equalsIgnoreCase(displayedCounterName)) {
return false;
}
}
return true;
}
/**
* @return Nom de l'application courante et nom du sous-répertoire de stockage dans une application monitorée.
*/
public static String getCurrentApplication() {
// use explicitly configured application name (if configured)
final String applicationName = Parameter.APPLICATION_NAME.getValue();
if (applicationName != null) {
return applicationName;
}
if (servletContext != null) {
// Le nom de l'application et donc le stockage des fichiers est dans le sous-répertoire
// ayant pour nom le contexte de la webapp et le nom du serveur
// pour pouvoir monitorer plusieurs webapps sur le même serveur et
// pour pouvoir stocker sur un répertoire partagé entre plusieurs serveurs
return getContextPath(servletContext) + '_' + getHostName();
}
return null;
}
public static String getContextPath(ServletContext context) {
// cette méthode retourne le contextPath de la webapp
// en utilisant ServletContext.getContextPath si servlet api 2.5
// ou en se débrouillant sinon
// (on n'a pas encore pour l'instant de request pour appeler HttpServletRequest.getContextPath)
if (context.getMajorVersion() == 2 && context.getMinorVersion() >= 5
|| context.getMajorVersion() > 2) {
// api servlet 2.5 (Java EE 5) minimum pour appeler ServletContext.getContextPath
return context.getContextPath();
}
final URL webXmlUrl;
try {
webXmlUrl = context.getResource("/WEB-INF/web.xml");
} catch (final MalformedURLException e) {
throw new IllegalStateException(e);
}
String contextPath = webXmlUrl.toExternalForm();
contextPath = contextPath.substring(0, contextPath.indexOf("/WEB-INF/web.xml"));
final int indexOfWar = contextPath.indexOf(".war");
if (indexOfWar > 0) {
contextPath = contextPath.substring(0, indexOfWar);
}
// tomcat peut renvoyer une url commençant pas "jndi:/localhost"
// (v5.5.28, webapp dans un répertoire)
if (contextPath.startsWith("jndi:/localhost")) {
contextPath = contextPath.substring("jndi:/localhost".length());
}
final int lastIndexOfSlash = contextPath.lastIndexOf('/');
if (lastIndexOfSlash != -1) {
contextPath = contextPath.substring(lastIndexOfSlash);
}
return contextPath;
}
private static String getJavaMelodyVersion() {
final InputStream inputStream = Parameters.class
.getResourceAsStream("/JAVAMELODY-VERSION.properties");
if (inputStream == null) {
return null;
}
final Properties properties = new Properties();
try {
try {
properties.load(inputStream);
return properties.getProperty("version");
} finally {
inputStream.close();
}
} catch (final IOException e) {
return e.toString();
}
}
/**
* Recherche la valeur d'un paramètre qui peut être défini par ordre de priorité croissant :
* - dans les paramètres d'initialisation du filtre (fichier web.xml dans la webapp)
* - dans les paramètres du contexte de la webapp avec le préfixe "javamelody." (fichier xml de contexte dans Tomcat)
* - dans les variables d'environnement du système d'exploitation avec le préfixe "javamelody."
* - dans les propriétés systèmes avec le préfixe "javamelody." (commande de lancement java).
* @param parameter Enum du paramètre
* @return valeur du paramètre ou null si pas de paramètre défini
*/
public static String getParameterValue(Parameter parameter) {
assert parameter != null;
final String name = parameter.getCode();
return getParameterValueByName(name);
}
public static String getParameterValueByName(String parameterName) {
assert parameterName != null;
final String globalName = PARAMETER_SYSTEM_PREFIX + parameterName;
String result = System.getProperty(globalName);
if (result != null) {
return result;
}
if (servletContext != null) {
result = servletContext.getInitParameter(globalName);
if (result != null) {
return result;
}
// issue 463: in a ServletContextListener, it's also possible to call servletContext.setAttribute("javamelody.log", "true"); for example
final Object attribute = servletContext.getAttribute(globalName);
if (attribute instanceof String) {
return (String) attribute;
}
}
if (filterConfig != null) {
return filterConfig.getInitParameter(parameterName);
}
return null;
}
}