/* * 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; import java.io.IOException; import java.io.InputStream; import java.net.HttpURLConnection; import java.net.URL; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.sql.Connection; import java.sql.DatabaseMetaData; import java.sql.DriverManager; import java.sql.SQLException; import java.util.ArrayList; import java.util.Calendar; import java.util.Date; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Properties; import java.util.Timer; import java.util.TimerTask; import javax.sql.DataSource; /** * Checks for update of javamelody version. * @author Emeric Vernat */ final class UpdateChecker { static final String COLLECTOR_SERVER_APPLICATION_TYPE = "Collector server"; private static final char SEPARATOR = '|'; private static final String SERVER_URL = "http://javamelody.org/usage/stats"; private static String newJavamelodyVersion; private final Collector collector; private final String applicationType; private final String serverUrl; private UpdateChecker(final Collector collector, final String applicationType, final String serverUrl) { super(); assert applicationType != null; assert collector != null || COLLECTOR_SERVER_APPLICATION_TYPE.equals(applicationType); this.collector = collector; this.applicationType = applicationType; this.serverUrl = serverUrl; } static void init(final Timer timer, final Collector collector, final String applicationType) { final String updateCheckDisabled = Parameters.getParameter(Parameter.UPDATE_CHECK_DISABLED); if (updateCheckDisabled == null || !Boolean.parseBoolean(updateCheckDisabled)) { final UpdateChecker updateChecker = new UpdateChecker(collector, applicationType, SERVER_URL); final TimerTask updateCheckerTimerTask = new TimerTask() { @Override public void run() { try { updateChecker.checkForUpdate(); } catch (final Throwable t) { // NOPMD // probablement pas connecté à Internet, tant pis } } }; // on laisse 10 minutes pour que la webapp démarre tranquillement, puis toutes les 24h timer.scheduleAtFixedRate(updateCheckerTimerTask, 10L * 60 * 1000, 24L * 60 * 60 * 1000); } } static UpdateChecker createForTest(final Collector collector, final String applicationType, final String serverUrl) { return new UpdateChecker(collector, applicationType, serverUrl); } static String getNewJavamelodyVersion() { return newJavamelodyVersion; } private static void setNewJavamelodyVersion(final String javamelodyVersion) { newJavamelodyVersion = javamelodyVersion; } void checkForUpdate() throws IOException { final String anonymousData = getAnonymousData(); final HttpURLConnection connection = (HttpURLConnection) new URL(serverUrl) .openConnection(); connection.setUseCaches(false); connection.setDoOutput(true); connection.setRequestMethod("POST"); connection.setConnectTimeout(60000); connection.setReadTimeout(60000); connection.setRequestProperty("data", anonymousData); connection.connect(); final Properties properties = new Properties(); final InputStream input = connection.getInputStream(); try { properties.load(input); } finally { input.close(); } final String javamelodyVersion = properties.getProperty("version"); if (javamelodyVersion != null && Parameters.JAVAMELODY_VERSION != null && javamelodyVersion.compareTo(Parameters.JAVAMELODY_VERSION) > 0) { setNewJavamelodyVersion(javamelodyVersion); } } private String getAnonymousData() throws IOException { final JavaInformations javaInformations = new JavaInformations( Parameters.getServletContext(), true); // compute a hash number as unique id final String uniqueId = hash( Parameters.getHostAddress() + '_' + javaInformations.getContextPath()); final String javamelodyVersion = Parameters.JAVAMELODY_VERSION; final String serverInfo = javaInformations.getServerInfo(); final String javaVersion = javaInformations.getJavaVersion(); final String jvmVersion = javaInformations.getJvmVersion(); final String maxMemory = String .valueOf(javaInformations.getMemoryInformations().getMaxMemory() / 1024 / 1024); final String availableProcessors = String .valueOf(javaInformations.getAvailableProcessors()); final String os = javaInformations.getOS(); final String databases = getDatabasesUsed(); final String countersUsed = getCountersUsed(); final String parametersUsed = getParametersUsed(); final String featuresUsed = getFeaturesUsed(javaInformations); final String locale = Locale.getDefault().toString(); final long usersMean = getUsersMean(); final int collectorApplications; if (COLLECTOR_SERVER_APPLICATION_TYPE.equals(applicationType)) { collectorApplications = Parameters.getCollectorUrlsByApplications().size(); } else { collectorApplications = -1; } return "{uniqueId=" + encode(uniqueId) + ", serverInfo=" + encode(serverInfo) + ", javamelodyVersion=" + encode(javamelodyVersion) + ", applicationType=" + encode(applicationType) + ", javaVersion=" + encode(javaVersion) + ", jvmVersion=" + encode(jvmVersion) + ", maxMemory=" + encode(maxMemory) + ", availableProcessors=" + encode(availableProcessors) + ", os=" + encode(os) + ", databases=" + encode(databases) + ", countersUsed=" + encode(countersUsed) + ", parametersUsed=" + encode(parametersUsed) + ", featuresUsed=" + encode(featuresUsed) + ", locale=" + encode(locale) + ", usersMean=" + usersMean + ", collectorApplications=" + collectorApplications + '}'; } private long getUsersMean() throws IOException { if (collector != null) { final JRobin httpSessionsJRobin = collector.getJRobin("httpSessions"); if (httpSessionsJRobin != null) { final double usersMean = httpSessionsJRobin.getMeanValue(getYesterdayRange()); // round to closest integer return Math.round(usersMean); } } return 0; } private Range getYesterdayRange() { final Calendar calendar = Calendar.getInstance(); calendar.set(Calendar.HOUR_OF_DAY, 0); calendar.set(Calendar.MINUTE, 0); calendar.set(Calendar.SECOND, 0); calendar.set(Calendar.MILLISECOND, 0); final Date endDate = calendar.getTime(); calendar.add(Calendar.DAY_OF_YEAR, -1); final Date startDate = calendar.getTime(); return Range.createCustomRange(startDate, endDate); } private String getParametersUsed() { final StringBuilder sb = new StringBuilder(); for (final Parameter parameter : Parameter.values()) { final String value = Parameters.getParameter(parameter); if (value != null) { if (sb.length() != 0) { sb.append(SEPARATOR); } sb.append(parameter.getCode()); } } return sb.toString(); } private String getCountersUsed() { if (collector == null) { return ""; } try { final List<Counter> counters = collector .getRangeCountersToBeDisplayed(Period.TOUT.getRange()); final StringBuilder sb = new StringBuilder(); for (final Counter counter : counters) { if (sb.length() != 0) { sb.append(SEPARATOR); } sb.append(counter.getName()); } return sb.toString(); } catch (final IOException e) { return e.getClass().getSimpleName(); } } private String getFeaturesUsed(JavaInformations javaInformations) { final List<String> features = new ArrayList<String>(); if (HtmlAbstractReport.isPdfEnabled()) { features.add("pdf"); } if (javaInformations.isCacheEnabled()) { features.add("caches"); } if (javaInformations.isJobEnabled()) { features.add("jobs"); } if (features.isEmpty()) { return ""; } final StringBuilder sb = new StringBuilder(); for (final String s : features) { if (sb.length() != 0) { sb.append(SEPARATOR); } sb.append(s); } return sb.toString(); } private String getDatabasesUsed() { if (Parameters.isNoDatabase()) { return ""; } final StringBuilder result = new StringBuilder(); try { if (Parameters.getLastConnectUrl() != null) { final Connection connection = DriverManager.getConnection( Parameters.getLastConnectUrl(), Parameters.getLastConnectInfo()); connection.setAutoCommit(false); return getDatabaseInfo(connection); } final Map<String, DataSource> dataSources = JdbcWrapper.getJndiAndSpringDataSources(); for (final DataSource dataSource : dataSources.values()) { final Connection connection = dataSource.getConnection(); if (result.length() > 0) { result.append(SEPARATOR); } result.append(getDatabaseInfo(connection)); } } catch (final Exception e) { result.append(e.toString()); } return result.toString(); } private String getDatabaseInfo(final Connection connection) throws SQLException { try { final DatabaseMetaData metaData = connection.getMetaData(); return metaData.getDatabaseProductName() + ' ' + metaData.getDatabaseProductVersion(); } finally { connection.close(); } } private static String encode(final String s) { if (s != null) { return "\"" + s.replace("\\", "\\\\").replace("\"", "\\\"").replace('\n', ' ') .replace('\r', ' ') + "\""; } return null; } private static MessageDigest getMessageDigestInstance() { // SHA1 est un algorithme de hashage qui évite les conflits à 2^80 près entre // les identifiants supposés uniques (SHA1 est mieux que MD5 qui est mieux que CRC32). try { return MessageDigest.getInstance("SHA-1"); } catch (final NoSuchAlgorithmException e) { // ne peut arriver car SHA1 est un algorithme disponible par défaut dans le JDK Sun throw new IllegalStateException(e); } } private static String hash(String value) { final MessageDigest messageDigest = getMessageDigestInstance(); messageDigest.update(value.getBytes()); final byte[] digest = messageDigest.digest(); final StringBuilder sb = new StringBuilder(digest.length * 2); // encodage en chaîne hexadécimale, // puisque les caractères bizarres ne peuvent être utilisés sur un système de fichiers int j; for (final byte element : digest) { j = element < 0 ? 256 + element : element; if (j < 16) { sb.append('0'); } sb.append(Integer.toHexString(j)); } return sb.toString(); } }