1
18 package net.bull.javamelody.internal.model;
19
20 import java.io.IOException;
21 import java.io.InputStream;
22 import java.net.HttpURLConnection;
23 import java.net.URL;
24 import java.security.MessageDigest;
25 import java.security.NoSuchAlgorithmException;
26 import java.sql.Connection;
27 import java.sql.DatabaseMetaData;
28 import java.sql.DriverManager;
29 import java.sql.SQLException;
30 import java.util.ArrayList;
31 import java.util.Calendar;
32 import java.util.Date;
33 import java.util.List;
34 import java.util.Locale;
35 import java.util.Map;
36 import java.util.Properties;
37 import java.util.Timer;
38 import java.util.TimerTask;
39
40 import javax.sql.DataSource;
41
42 import net.bull.javamelody.JdbcWrapper;
43 import net.bull.javamelody.Parameter;
44 import net.bull.javamelody.internal.common.Parameters;
45
46
50 public final class UpdateChecker {
51 static final String COLLECTOR_SERVER_APPLICATION_TYPE = "Collector server";
52
53 private static final char SEPARATOR = '|';
54
55 private static final String SERVER_URL = "http:;
56
57 private static String newJavamelodyVersion;
58
59 private final Collector collector;
60
61 private final String applicationType;
62
63 private final String serverUrl;
64
65 private UpdateChecker(final Collector collector, final String applicationType,
66 final String serverUrl) {
67 super();
68 assert applicationType != null;
69 assert collector != null || COLLECTOR_SERVER_APPLICATION_TYPE.equals(applicationType);
70 this.collector = collector;
71 this.applicationType = applicationType;
72 this.serverUrl = serverUrl;
73 }
74
75 public static void init(Timer timer, Collector collector, String applicationType) {
76 if (!Parameter.UPDATE_CHECK_DISABLED.getValueAsBoolean()) {
77 final UpdateChecker updateChecker = new UpdateChecker(collector, applicationType,
78 SERVER_URL);
79 final TimerTask updateCheckerTimerTask = new TimerTask() {
80 @Override
81 public void run() {
82 try {
83 updateChecker.checkForUpdate();
84 } catch (final Throwable t) {
85
86 }
87 }
88 };
89
90 timer.scheduleAtFixedRate(updateCheckerTimerTask, 10L * 60 * 1000,
91 24L * 60 * 60 * 1000);
92 }
93 }
94
95 static UpdateChecker createForTest(final Collector collector, final String applicationType,
96 final String serverUrl) {
97 return new UpdateChecker(collector, applicationType, serverUrl);
98 }
99
100 public static String getNewJavamelodyVersion() {
101 return newJavamelodyVersion;
102 }
103
104 private static void setNewJavamelodyVersion(final String javamelodyVersion) {
105 newJavamelodyVersion = javamelodyVersion;
106 }
107
108 void checkForUpdate() throws IOException {
109 final String anonymousData = getAnonymousData();
110 final HttpURLConnection connection = (HttpURLConnection) new URL(serverUrl)
111 .openConnection();
112 connection.setUseCaches(false);
113 connection.setDoOutput(true);
114 connection.setRequestMethod("POST");
115 connection.setConnectTimeout(60000);
116 connection.setReadTimeout(60000);
117 connection.setRequestProperty("data", anonymousData);
118 connection.connect();
119
120 final Properties properties = new Properties();
121 try (InputStream input = connection.getInputStream()) {
122 properties.load(input);
123 }
124 final String javamelodyVersion = properties.getProperty("version");
125 if (javamelodyVersion != null && Parameters.JAVAMELODY_VERSION != null
126 && javamelodyVersion.compareTo(Parameters.JAVAMELODY_VERSION) > 0) {
127 setNewJavamelodyVersion(javamelodyVersion);
128 }
129 }
130
131 private String getAnonymousData() throws IOException {
132 final JavaInformations javaInformations = new JavaInformations(
133 Parameters.getServletContext(), true);
134
135 final String uniqueId = hash(
136 Parameters.getHostAddress() + '_' + javaInformations.getContextPath());
137 final String javamelodyVersion = Parameters.JAVAMELODY_VERSION;
138 final String serverInfo = javaInformations.getServerInfo();
139 final String javaVersion = javaInformations.getJavaVersion();
140 final String jvmVersion = javaInformations.getJvmVersion();
141 final String maxMemory = String
142 .valueOf(javaInformations.getMemoryInformations().getMaxMemory() / 1024 / 1024);
143 final String availableProcessors = String
144 .valueOf(javaInformations.getAvailableProcessors());
145 final String os = javaInformations.getOS();
146 final String databases = getDatabasesUsed();
147 final String countersUsed = getCountersUsed();
148 final String parametersUsed = getParametersUsed();
149 final String featuresUsed = getFeaturesUsed(javaInformations);
150 final String locale = Locale.getDefault().toString();
151 final long usersMean = getUsersMean();
152 final int collectorApplications;
153 if (COLLECTOR_SERVER_APPLICATION_TYPE.equals(applicationType)) {
154 collectorApplications = Parameters.getCollectorUrlsByApplications().size();
155 } else {
156 collectorApplications = -1;
157 }
158
159 return "{uniqueId=" + encode(uniqueId) + ", serverInfo=" + encode(serverInfo)
160 + ", javamelodyVersion=" + encode(javamelodyVersion) + ", applicationType="
161 + encode(applicationType) + ", javaVersion=" + encode(javaVersion) + ", jvmVersion="
162 + encode(jvmVersion) + ", maxMemory=" + encode(maxMemory) + ", availableProcessors="
163 + encode(availableProcessors) + ", os=" + encode(os) + ", databases="
164 + encode(databases) + ", countersUsed=" + encode(countersUsed) + ", parametersUsed="
165 + encode(parametersUsed) + ", featuresUsed=" + encode(featuresUsed) + ", locale="
166 + encode(locale) + ", usersMean=" + usersMean + ", collectorApplications="
167 + collectorApplications + '}';
168 }
169
170 private long getUsersMean() throws IOException {
171 if (collector != null) {
172 final JRobin httpSessionsJRobin = collector.getJRobin("httpSessions");
173 if (httpSessionsJRobin != null) {
174 final double usersMean = httpSessionsJRobin.getMeanValue(getYesterdayRange());
175
176 return Math.round(usersMean);
177 }
178 }
179 return 0;
180 }
181
182 private Range getYesterdayRange() {
183 final Calendar calendar = Calendar.getInstance();
184 calendar.set(Calendar.HOUR_OF_DAY, 0);
185 calendar.set(Calendar.MINUTE, 0);
186 calendar.set(Calendar.SECOND, 0);
187 calendar.set(Calendar.MILLISECOND, 0);
188 final Date endDate = calendar.getTime();
189 calendar.add(Calendar.DAY_OF_YEAR, -1);
190 final Date startDate = calendar.getTime();
191 return Range.createCustomRange(startDate, endDate);
192 }
193
194 private String getParametersUsed() {
195 final StringBuilder sb = new StringBuilder();
196 for (final Parameter parameter : Parameter.values()) {
197 final String value = parameter.getValue();
198 if (value != null) {
199 if (sb.length() != 0) {
200 sb.append(SEPARATOR);
201 }
202 sb.append(parameter.getCode());
203 }
204 }
205 return sb.toString();
206 }
207
208 private String getCountersUsed() {
209 if (collector == null) {
210 return "";
211 }
212 try {
213 final List<Counter> counters = collector
214 .getRangeCountersToBeDisplayed(Period.TOUT.getRange());
215 final StringBuilder sb = new StringBuilder();
216 for (final Counter counter : counters) {
217 if (sb.length() != 0) {
218 sb.append(SEPARATOR);
219 }
220 sb.append(counter.getName());
221 }
222 return sb.toString();
223 } catch (final IOException e) {
224 return e.getClass().getSimpleName();
225 }
226 }
227
228 private String getFeaturesUsed(JavaInformations javaInformations) {
229 final List<String> features = new ArrayList<>();
230 if (Parameters.isPdfEnabled()) {
231 features.add("pdf");
232 }
233 if (javaInformations.isCacheEnabled()) {
234 features.add("caches");
235 }
236 if (javaInformations.isJobEnabled()) {
237 features.add("jobs");
238 }
239 if (features.isEmpty()) {
240 return "";
241 }
242 final StringBuilder sb = new StringBuilder();
243 for (final String s : features) {
244 if (sb.length() != 0) {
245 sb.append(SEPARATOR);
246 }
247 sb.append(s);
248 }
249 return sb.toString();
250 }
251
252 private String getDatabasesUsed() {
253 if (Parameters.isNoDatabase()) {
254 return "";
255 }
256 final StringBuilder result = new StringBuilder();
257 try {
258 if (Parameters.getLastConnectUrl() != null) {
259 final Connection connection = DriverManager.getConnection(
260 Parameters.getLastConnectUrl(), Parameters.getLastConnectInfo());
261 connection.setAutoCommit(false);
262 return getDatabaseInfo(connection);
263 }
264
265 final Map<String, DataSource> dataSources = JdbcWrapper.getJndiAndSpringDataSources();
266 for (final DataSource dataSource : dataSources.values()) {
267 final Connection connection = dataSource.getConnection();
268 if (result.length() > 0) {
269 result.append(SEPARATOR);
270 }
271 result.append(getDatabaseInfo(connection));
272 }
273 } catch (final Exception e) {
274 result.append(e);
275 }
276 return result.toString();
277 }
278
279 private String getDatabaseInfo(final Connection connection) throws SQLException {
280 try {
281 final DatabaseMetaData metaData = connection.getMetaData();
282 return metaData.getDatabaseProductName() + ' ' + metaData.getDatabaseProductVersion();
283 } finally {
284 connection.close();
285 }
286 }
287
288 private static String encode(final String s) {
289 if (s != null) {
290 return "\"" + s.replace("\\", "\\\\").replace("\"", "\\\"").replace('\n', ' ')
291 .replace('\r', ' ') + "\"";
292 }
293 return null;
294 }
295
296 private static MessageDigest getMessageDigestInstance() {
297
298
299 try {
300 return MessageDigest.getInstance("SHA-1");
301 } catch (final NoSuchAlgorithmException e) {
302
303 throw new IllegalStateException(e);
304 }
305 }
306
307 private static String hash(String value) {
308 final MessageDigest messageDigest = getMessageDigestInstance();
309 messageDigest.update(value.getBytes());
310 final byte[] digest = messageDigest.digest();
311
312 final StringBuilder sb = new StringBuilder(digest.length * 2);
313
314
315 int j;
316 for (final byte element : digest) {
317 j = element < 0 ? 256 + element : element;
318 if (j < 16) {
319 sb.append('0');
320 }
321 sb.append(Integer.toHexString(j));
322 }
323
324 return sb.toString();
325 }
326 }
327