/* * 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/<context> 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<String, List<URL>> urlsByApplications; private static Map<String, List<String>> 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<String, List<URL>> getCollectorUrlsByApplications() throws IOException { if (urlsByApplications == null) { readCollectorApplications(); } return Collections.unmodifiableMap(urlsByApplications); } public static Map<String, List<String>> getApplicationsByAggregationApplication() throws IOException { if (applicationsByAggregationApplications == null) { readCollectorApplications(); } return Collections.unmodifiableMap(applicationsByAggregationApplications); } public static void addCollectorApplication(String application, List<URL> 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<String> 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<String, List<URL>> entry : urlsByApplications.entrySet()) { final List<URL> 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<String, List<String>> entry : applicationsByAggregationApplications .entrySet()) { final List<String> 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()); } final FileOutputStream output = new FileOutputStream(collectorApplicationsFile); try { properties.store(output, "urls of the applications to monitor"); } finally { output.close(); } } 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<String, List<URL>> applications = new TreeMap<String, List<URL>>(); final Map<String, List<String>> aggregationApplications = new TreeMap<String, List<String>>(); final File file = getCollectorApplicationsFile(); if (file.exists()) { final Properties properties = new Properties(); final FileInputStream input = new FileInputStream(file); try { properties.load(input); } finally { input.close(); } @SuppressWarnings("unchecked") final List<String> propertyNames = (List<String>) 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<String>(Arrays.asList(value.split(",")))); } } } urlsByApplications = applications; applicationsByAggregationApplications = aggregationApplications; synchronizeAggregationApplications(); } private static void synchronizeAggregationApplications() { for (final Iterator<List<String>> it1 = applicationsByAggregationApplications.values() .iterator(); it1.hasNext();) { final List<String> aggregatedApplications = it1.next(); for (final Iterator<String> 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<URL> 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<URL> urls = new ArrayList<URL>(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 : <br/> * - dans les paramètres d'initialisation du filtre (fichier web.xml dans la webapp) <br/> * - dans les paramètres du contexte de la webapp avec le préfixe "javamelody." (fichier xml de contexte dans Tomcat) <br/> * - dans les variables d'environnement du système d'exploitation avec le préfixe "javamelody." <br/> * - 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; } }