1 /*
2  * Copyright 2008-2019 by Emeric Vernat
3  *
4  *     This file is part of Java Melody.
5  *
6  * Licensed under the Apache License, Version 2.0 (the "License");
7  * you may not use this file except in compliance with the License.
8  * You may obtain a copy of the License at
9  *
10  *     http://www.apache.org/licenses/LICENSE-2.0
11  *
12  * Unless required by applicable law or agreed to in writing, software
13  * distributed under the License is distributed on an "AS IS" BASIS,
14  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  * See the License for the specific language governing permissions and
16  * limitations under the License.
17  */

18 package net.bull.javamelody.internal.model; // NOPMD
19
20 import java.io.Serializable;
21 import java.lang.management.ManagementFactory;
22 import java.lang.management.OperatingSystemMXBean;
23 import java.lang.management.ThreadMXBean;
24 import java.sql.Connection;
25 import java.sql.DatabaseMetaData;
26 import java.sql.DriverManager;
27 import java.sql.SQLException;
28 import java.util.ArrayList;
29 import java.util.Arrays;
30 import java.util.Collections;
31 import java.util.Comparator;
32 import java.util.Date;
33 import java.util.List;
34 import java.util.Locale;
35 import java.util.Map;
36
37 import javax.servlet.ServletContext;
38 import javax.sql.DataSource;
39
40 import net.bull.javamelody.JdbcWrapper;
41 import net.bull.javamelody.SessionListener;
42 import net.bull.javamelody.SpringContext;
43 import net.bull.javamelody.internal.common.Parameters;
44 import net.bull.javamelody.internal.model.HsErrPid.HsErrPidComparator;
45
46 /**
47  * Informations systèmes sur le serveur, sans code html de présentation.
48  * L'état d'une instance est initialisé à son instanciation et non mutable;
49  * il est donc de fait thread-safe.
50  * Cet état est celui d'une instance de JVM java, de ses threads et du système à un instant t.
51  * Les instances sont sérialisables pour pouvoir être transmises au serveur de collecte.
52  * @author Emeric Vernat
53  */

54 public class JavaInformations implements Serializable { // NOPMD
55     public static final double HIGH_USAGE_THRESHOLD_IN_PERCENTS = 95d;
56     private static final long serialVersionUID = 3281861236369720876L;
57     private static final Date START_DATE = new Date();
58     private static final boolean SPRING_AVAILABLE = isSpringAvailable();
59     private static boolean localWebXmlExists = true// true par défaut
60     private static boolean localPomXmlExists = true// true par défaut
61     private final MemoryInformations memoryInformations;
62     @SuppressWarnings("all")
63     private final List<TomcatInformations> tomcatInformationsList;
64     private final int sessionCount;
65     private final long sessionAgeSum;
66     private final int activeThreadCount;
67     private final int usedConnectionCount;
68     private final int maxConnectionCount;
69     private final int activeConnectionCount;
70     private final long transactionCount;
71     private final long processCpuTimeMillis;
72     private final double systemLoadAverage;
73     private final double systemCpuLoad;
74     private final long unixOpenFileDescriptorCount;
75     private final long unixMaxFileDescriptorCount;
76     private final String host;
77     private final String os;
78     private final int availableProcessors;
79     private final String javaVersion;
80     private final String jvmVersion;
81     private final String pid;
82     private final String serverInfo;
83     private final String contextPath;
84     private final String contextDisplayName;
85     private final String webappVersion;
86     private final Date startDate;
87     private final String jvmArguments;
88     private final long freeDiskSpaceInTemp;
89     private final long usableDiskSpaceInTemp;
90     private final int threadCount;
91     private final int peakThreadCount;
92     private final long totalStartedThreadCount;
93     private final String dataBaseVersion;
94     private final String dataSourceDetails;
95     @SuppressWarnings("all")
96     private final List<ThreadInformations> threadInformationsList;
97     @SuppressWarnings("all")
98     private final List<CacheInformations> cacheInformationsList;
99     @SuppressWarnings("all")
100     private final List<JCacheInformations> jcacheInformationsList;
101     @SuppressWarnings("all")
102     private final List<JobInformations> jobInformationsList;
103     @SuppressWarnings("all")
104     private final List<HsErrPid> hsErrPidList;
105     private final boolean webXmlExists = localWebXmlExists;
106     private final boolean pomXmlExists = localPomXmlExists;
107     private final boolean springBeanExists;
108
109     static final class ThreadInformationsComparator
110             implements Comparator<ThreadInformations>, Serializable {
111         private static final long serialVersionUID = 1L;
112
113         /** {@inheritDoc} */
114         @Override
115         public int compare(ThreadInformations thread1, ThreadInformations thread2) {
116             return thread1.getName().compareToIgnoreCase(thread2.getName());
117         }
118     }
119
120     static final class CacheInformationsComparator
121             implements Comparator<CacheInformations>, Serializable {
122         private static final long serialVersionUID = 1L;
123
124         /** {@inheritDoc} */
125         @Override
126         public int compare(CacheInformations cache1, CacheInformations cache2) {
127             return cache1.getName().compareToIgnoreCase(cache2.getName());
128         }
129     }
130
131     static final class JCacheInformationsComparator
132             implements Comparator<JCacheInformations>, Serializable {
133         private static final long serialVersionUID = 1L;
134
135         /** {@inheritDoc} */
136         @Override
137         public int compare(JCacheInformations cache1, JCacheInformations cache2) {
138             return cache1.getName().compareToIgnoreCase(cache2.getName());
139         }
140     }
141
142     static final class JobInformationsComparator
143             implements Comparator<JobInformations>, Serializable {
144         private static final long serialVersionUID = 1L;
145
146         /** {@inheritDoc} */
147         @Override
148         public int compare(JobInformations job1, JobInformations job2) {
149             return job1.getName().compareToIgnoreCase(job2.getName());
150         }
151     }
152
153     // CHECKSTYLE:OFF
154     public JavaInformations(ServletContext servletContext, boolean includeDetails) {
155         // CHECKSTYLE:ON
156         super();
157         memoryInformations = new MemoryInformations();
158         tomcatInformationsList = TomcatInformations.buildTomcatInformationsList();
159         sessionCount = SessionListener.getSessionCount();
160         sessionAgeSum = SessionListener.getSessionAgeSum();
161         activeThreadCount = JdbcWrapper.getActiveThreadCount();
162         usedConnectionCount = JdbcWrapper.getUsedConnectionCount();
163         activeConnectionCount = JdbcWrapper.getActiveConnectionCount();
164         maxConnectionCount = JdbcWrapper.getMaxConnectionCount();
165         transactionCount = JdbcWrapper.getTransactionCount();
166         systemLoadAverage = buildSystemLoadAverage();
167         systemCpuLoad = buildSystemCpuLoad();
168         processCpuTimeMillis = buildProcessCpuTimeMillis();
169         unixOpenFileDescriptorCount = buildOpenFileDescriptorCount();
170         unixMaxFileDescriptorCount = buildMaxFileDescriptorCount();
171         host = Parameters.getHostName() + '@' + Parameters.getHostAddress();
172         os = buildOS();
173         availableProcessors = Runtime.getRuntime().availableProcessors();
174         javaVersion = System.getProperty("java.runtime.name") + ", "
175                 + System.getProperty("java.runtime.version");
176         jvmVersion = System.getProperty("java.vm.name") + ", "
177                 + System.getProperty("java.vm.version") + ", " + System.getProperty("java.vm.info");
178         if (servletContext == null) {
179             serverInfo = null;
180             contextPath = null;
181             contextDisplayName = null;
182             webappVersion = null;
183         } else {
184             serverInfo = servletContext.getServerInfo();
185             contextPath = Parameters.getContextPath(servletContext);
186             contextDisplayName = servletContext.getServletContextName();
187             webappVersion = MavenArtifact.getWebappVersion();
188         }
189         startDate = START_DATE;
190         jvmArguments = buildJvmArguments();
191         final ThreadMXBean threadBean = ManagementFactory.getThreadMXBean();
192         threadCount = threadBean.getThreadCount();
193         peakThreadCount = threadBean.getPeakThreadCount();
194         totalStartedThreadCount = threadBean.getTotalStartedThreadCount();
195         freeDiskSpaceInTemp = Parameters.TEMPORARY_DIRECTORY.getFreeSpace();
196         usableDiskSpaceInTemp = Parameters.TEMPORARY_DIRECTORY.getUsableSpace();
197         springBeanExists = SPRING_AVAILABLE && SpringContext.getSingleton() != null;
198
199         if (includeDetails) {
200             dataBaseVersion = buildDataBaseVersion();
201             dataSourceDetails = buildDataSourceDetails();
202             threadInformationsList = buildThreadInformationsList();
203             cacheInformationsList = CacheInformations.buildCacheInformationsList();
204             jcacheInformationsList = JCacheInformations.buildJCacheInformationsList();
205             jobInformationsList = JobInformations.buildJobInformationsList();
206             hsErrPidList = HsErrPid.buildHsErrPidList();
207             pid = PID.getPID();
208         } else {
209             dataBaseVersion = null;
210             dataSourceDetails = null;
211             threadInformationsList = null;
212             cacheInformationsList = null;
213             jcacheInformationsList = null;
214             jobInformationsList = null;
215             hsErrPidList = null;
216             pid = null;
217         }
218     }
219
220     public static void setWebXmlExistsAndPomXmlExists(boolean webXmlExists, boolean pomXmlExists) {
221         localWebXmlExists = webXmlExists;
222         localPomXmlExists = pomXmlExists;
223     }
224
225     public boolean doesWebXmlExists() {
226         return webXmlExists;
227     }
228
229     public boolean doesPomXmlExists() {
230         return pomXmlExists;
231     }
232
233     private static String buildOS() {
234         final String name = System.getProperty("os.name");
235         final String version = System.getProperty("os.version");
236         final String patchLevel = System.getProperty("sun.os.patch.level");
237         final String arch = System.getProperty("os.arch");
238         final String bits = System.getProperty("sun.arch.data.model");
239
240         final StringBuilder sb = new StringBuilder();
241         sb.append(name).append(", ");
242         if (!name.toLowerCase(Locale.ENGLISH).contains("windows")) {
243             // version is "6.1" and useless for os.name "Windows 7",
244             // and can be "2.6.32-358.23.2.el6.x86_64" for os.name "Linux"
245             sb.append(version).append(' ');
246         }
247         if (!"unknown".equals(patchLevel)) {
248             // patchLevel is "unknown" and useless on Linux,
249             // and can be "Service Pack 1" on Windows
250             sb.append(patchLevel);
251         }
252         sb.append(", ").append(arch).append('/').append(bits);
253         return sb.toString();
254     }
255
256     private static long buildProcessCpuTimeMillis() {
257         // nano-secondes converties en milli-secondes
258         final long processCpuTime = MBeansAccessor.getLongFromOperatingSystem("ProcessCpuTime");
259         if (processCpuTime >= 0L) {
260             return processCpuTime / 1000000;
261         }
262         return -1;
263     }
264
265     private static long buildOpenFileDescriptorCount() {
266         try {
267             return MBeansAccessor.getLongFromOperatingSystem("OpenFileDescriptorCount");
268         } catch (final Throwable e) { // NOPMD
269             // pour issues 16, 779 (using jsvc on ubuntu or debian)
270             // a priori, InternalError est catchée par le MBeanServer et encapsulé en RuntimeErrorException
271             return -1;
272         }
273     }
274
275     private static long buildMaxFileDescriptorCount() {
276         try {
277             return MBeansAccessor.getLongFromOperatingSystem("MaxFileDescriptorCount");
278         } catch (final Throwable e) { // NOPMD
279             // pour issues 16, 779 (using jsvc on ubuntu or debian)
280             // a priori, InternalError est catchée par le MBeanServer et encapsulé en RuntimeErrorException
281             return -1;
282         }
283     }
284
285     private static double buildSystemCpuLoad() {
286         // System cpu load.
287         // The "recent cpu usage" for the whole system.
288         // This value is a double in the [0.0,1.0] interval.
289         // A value of 0.0 means that all CPUs were idle during the recent period of time observed,
290         // while a value of 1.0 means that all CPUs were actively running 100% of the time during the recent period being observed.
291
292         final double systemCpu = MBeansAccessor.getDoubleFromOperatingSystem("SystemCpuLoad");
293         if (systemCpu >= 0D) {
294             return systemCpu * 100;
295         }
296         // systemCpuLoad n'existe qu'à partir du jdk 1.7
297         return -1;
298     }
299
300     private static double buildSystemLoadAverage() {
301         // System load average for the last minute.
302         // The system load average is the sum of
303         // the number of runnable entities queued to the available processors
304         // and the number of runnable entities running on the available processors
305         // averaged over a period of time.
306         final OperatingSystemMXBean operatingSystem = ManagementFactory.getOperatingSystemMXBean();
307         if (operatingSystem.getSystemLoadAverage() >= 0) {
308             // systemLoadAverage n'existe qu'à partir du jdk 1.6
309             return operatingSystem.getSystemLoadAverage();
310         }
311         return -1;
312     }
313
314     private static String buildJvmArguments() {
315         final StringBuilder jvmArgs = new StringBuilder();
316         for (final String jvmArg : ManagementFactory.getRuntimeMXBean().getInputArguments()) {
317             jvmArgs.append(jvmArg).append('\n');
318         }
319         if (jvmArgs.length() > 0) {
320             jvmArgs.deleteCharAt(jvmArgs.length() - 1);
321         }
322         return jvmArgs.toString();
323     }
324
325     public static List<ThreadInformations> buildThreadInformationsList() {
326         final ThreadMXBean threadBean = ManagementFactory.getThreadMXBean();
327         final Map<Thread, StackTraceElement[]> stackTraces = Thread.getAllStackTraces();
328         final List<Thread> threads = new ArrayList<>(stackTraces.keySet());
329
330         // si "1.6.0_01".compareTo(Parameters.JAVA_VERSION) > 0;
331         // on récupèrait les threads sans stack trace en contournant bug 6434648 avant 1.6.0_01
332         // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6434648
333         // hormis pour le thread courant qui obtient sa stack trace différemment sans le bug
334         //        threads = getThreadsFromThreadGroups();
335         //        final Thread currentThread = Thread.currentThread();
336         //        stackTraces = Collections.singletonMap(currentThread, currentThread.getStackTrace());
337
338         final boolean cpuTimeEnabled = threadBean.isThreadCpuTimeSupported()
339                 && threadBean.isThreadCpuTimeEnabled();
340         final long[] deadlockedThreads = getDeadlockedThreads(threadBean);
341         final List<ThreadInformations> threadInfosList = new ArrayList<>(threads.size());
342         // hostAddress récupéré ici car il peut y avoir plus de 20000 threads
343         final String hostAddress = Parameters.getHostAddress();
344         for (final Thread thread : threads) {
345             final StackTraceElement[] stackTraceElements = stackTraces.get(thread);
346             final List<StackTraceElement> stackTraceElementList = stackTraceElements == null ? null
347                     : new ArrayList<>(Arrays.asList(stackTraceElements));
348             final long cpuTimeMillis;
349             final long userTimeMillis;
350             if (cpuTimeEnabled) {
351                 cpuTimeMillis = threadBean.getThreadCpuTime(thread.getId()) / 1000000;
352                 userTimeMillis = threadBean.getThreadUserTime(thread.getId()) / 1000000;
353             } else {
354                 cpuTimeMillis = -1;
355                 userTimeMillis = -1;
356             }
357             final boolean deadlocked = deadlockedThreads != null
358                     && Arrays.binarySearch(deadlockedThreads, thread.getId()) >= 0;
359             // stackTraceElementList est une ArrayList et non unmodifiableList pour lisibilité xml
360             threadInfosList.add(new ThreadInformations(thread, stackTraceElementList, cpuTimeMillis,
361                     userTimeMillis, deadlocked, hostAddress));
362         }
363         // on retourne ArrayList et non unmodifiableList pour lisibilité du xml par xstream
364         return threadInfosList;
365     }
366
367     static List<Thread> getThreadsFromThreadGroups() {
368         ThreadGroup group = Thread.currentThread().getThreadGroup(); // NOPMD
369         while (group.getParent() != null) {
370             group = group.getParent();
371         }
372         final Thread[] threadsArray = new Thread[group.activeCount()];
373         group.enumerate(threadsArray, true);
374         final List<Thread> threads = new ArrayList<>(threadsArray.length);
375         for (final Thread thread : threadsArray) {
376             // threadsArray may contain null if a thread has just died between activeCount and enumerate
377             if (thread != null) {
378                 threads.add(thread);
379             }
380         }
381         return threads;
382     }
383
384     private static long[] getDeadlockedThreads(ThreadMXBean threadBean) {
385         final long[] deadlockedThreads;
386         if (threadBean.isSynchronizerUsageSupported()) {
387             deadlockedThreads = threadBean.findDeadlockedThreads();
388         } else {
389             deadlockedThreads = threadBean.findMonitorDeadlockedThreads();
390         }
391         if (deadlockedThreads != null) {
392             Arrays.sort(deadlockedThreads);
393         }
394         return deadlockedThreads;
395     }
396
397     private static String buildDataBaseVersion() {
398         if (Parameters.isNoDatabase()) {
399             return null;
400         }
401         final StringBuilder result = new StringBuilder();
402         try {
403             // on commence par voir si le driver jdbc a été utilisé
404             // car s'il n'y a pas de datasource une exception est déclenchée
405             if (Parameters.getLastConnectUrl() != null) {
406                 final Connection connection = DriverManager.getConnection(
407                         Parameters.getLastConnectUrl(), Parameters.getLastConnectInfo());
408                 connection.setAutoCommit(false);
409                 try {
410                     appendDataBaseVersion(result, connection);
411                 } finally {
412                     // rollback inutile ici car on ne fait que lire les meta-data (+ cf issue 38)
413                     connection.close();
414                 }
415             }
416
417             // on cherche une datasource avec InitialContext pour afficher nom et version bdd + nom et version driver jdbc
418             // (le nom de la dataSource recherchée dans JNDI est du genre jdbc/Xxx qui est le nom standard d'une DataSource)
419             final Map<String, DataSource> dataSources = JdbcWrapper.getJndiAndSpringDataSources();
420             for (final Map.Entry<String, DataSource> entry : dataSources.entrySet()) {
421                 final String name = entry.getKey();
422                 final DataSource dataSource = entry.getValue();
423                 // on ne doit pas changer autoCommit pour la connection d'une DataSource
424                 // (ou alors il faudrait remettre l'autoCommit après, issue 233)
425                 // connection.setAutoCommit(false);
426                 try (Connection connection = dataSource.getConnection()) {
427                     if (result.length() > 0) {
428                         result.append("\n\n");
429                     }
430                     result.append(name).append(":\n");
431                     appendDataBaseVersion(result, connection);
432                     // rollback inutile ici car on ne fait que lire les meta-data (+ cf issue 38)
433                 }
434             }
435         } catch (final Exception e) {
436             result.append(e);
437         }
438         if (result.length() > 0) {
439             return result.toString();
440         }
441         return null;
442     }
443
444     private static void appendDataBaseVersion(StringBuilder result, Connection connection)
445             throws SQLException {
446         final DatabaseMetaData metaData = connection.getMetaData();
447         // Sécurité: pour l'instant on n'indique pas metaData.getUserName()
448         final String url = metaData.getURL();
449         if (url != null) {
450             // no application puts a password in clear text in a URL
451             // since all the docs show with separate properties and it would be a fault in itself,
452             // but just in case
453             result.append(url.replaceAll("(?<=password=).*""\\$")).append('\n');
454         }
455         result.append(metaData.getDatabaseProductName()).append(", ")
456                 .append(metaData.getDatabaseProductVersion()).append('\n');
457         result.append("Driver JDBC:\n").append(metaData.getDriverName()).append(", ")
458                 .append(metaData.getDriverVersion());
459     }
460
461     private static String buildDataSourceDetails() {
462         final Map<String, Map<String, Object>> dataSourcesProperties = JdbcWrapper
463                 .getBasicDataSourceProperties();
464         final StringBuilder sb = new StringBuilder();
465         for (final Map.Entry<String, Map<String, Object>> entry : dataSourcesProperties
466                 .entrySet()) {
467             final Map<String, Object> dataSourceProperties = entry.getValue();
468             if (dataSourceProperties.isEmpty()) {
469                 continue;
470             }
471             if (sb.length() > 0) {
472                 sb.append('\n');
473             }
474             final String name = entry.getKey();
475             if (name != null) {
476                 sb.append(name).append(":\n");
477             }
478             for (final Map.Entry<String, Object> propertyEntry : dataSourceProperties.entrySet()) {
479                 sb.append(propertyEntry.getKey()).append(" = ").append(propertyEntry.getValue())
480                         .append('\n');
481             }
482         }
483         if (sb.length() == 0) {
484             return null;
485         }
486         return sb.toString();
487     }
488
489     public MemoryInformations getMemoryInformations() {
490         return memoryInformations;
491     }
492
493     public List<TomcatInformations> getTomcatInformationsList() {
494         return tomcatInformationsList;
495     }
496
497     public int getSessionCount() {
498         return sessionCount;
499     }
500
501     long getSessionAgeSum() {
502         return sessionAgeSum;
503     }
504
505     public long getSessionMeanAgeInMinutes() {
506         if (sessionCount > 0) {
507             return sessionAgeSum / sessionCount / 60000;
508         }
509         return -1;
510     }
511
512     public int getActiveThreadCount() {
513         return activeThreadCount;
514     }
515
516     public int getUsedConnectionCount() {
517         return usedConnectionCount;
518     }
519
520     public int getActiveConnectionCount() {
521         return activeConnectionCount;
522     }
523
524     public int getMaxConnectionCount() {
525         return maxConnectionCount;
526     }
527
528     public long getTransactionCount() {
529         return transactionCount;
530     }
531
532     public double getUsedConnectionPercentage() {
533         if (maxConnectionCount > 0) {
534             return 100d * usedConnectionCount / maxConnectionCount;
535         }
536         return -1d;
537     }
538
539     public long getProcessCpuTimeMillis() {
540         return processCpuTimeMillis;
541     }
542
543     public double getSystemLoadAverage() {
544         return systemLoadAverage;
545     }
546
547     public double getSystemCpuLoad() {
548         return systemCpuLoad;
549     }
550
551     public long getUnixOpenFileDescriptorCount() {
552         return unixOpenFileDescriptorCount;
553     }
554
555     public long getUnixMaxFileDescriptorCount() {
556         return unixMaxFileDescriptorCount;
557     }
558
559     public double getUnixOpenFileDescriptorPercentage() {
560         if (unixOpenFileDescriptorCount >= 0) {
561             return 100d * unixOpenFileDescriptorCount / unixMaxFileDescriptorCount;
562         }
563         return -1d;
564     }
565
566     public String getHost() {
567         return host;
568     }
569
570     public String getOS() {
571         return os;
572     }
573
574     public int getAvailableProcessors() {
575         return availableProcessors;
576     }
577
578     public String getJavaVersion() {
579         return javaVersion;
580     }
581
582     public String getJvmVersion() {
583         return jvmVersion;
584     }
585
586     public String getPID() {
587         return pid;
588     }
589
590     public String getServerInfo() {
591         return serverInfo;
592     }
593
594     public String getContextPath() {
595         return contextPath;
596     }
597
598     public String getContextDisplayName() {
599         return contextDisplayName;
600     }
601
602     public String getWebappVersion() {
603         return webappVersion;
604     }
605
606     public Date getStartDate() {
607         return startDate;
608     }
609
610     public String getJvmArguments() {
611         return jvmArguments;
612     }
613
614     public long getFreeDiskSpaceInTemp() {
615         return freeDiskSpaceInTemp;
616     }
617
618     public long getUsableDiskSpaceInTemp() {
619         return usableDiskSpaceInTemp;
620     }
621
622     public int getThreadCount() {
623         return threadCount;
624     }
625
626     public int getPeakThreadCount() {
627         return peakThreadCount;
628     }
629
630     public long getTotalStartedThreadCount() {
631         return totalStartedThreadCount;
632     }
633
634     public String getDataBaseVersion() {
635         return dataBaseVersion;
636     }
637
638     public String getDataSourceDetails() {
639         return dataSourceDetails;
640     }
641
642     public List<ThreadInformations> getThreadInformationsList() {
643         // on trie sur demande (si affichage)
644         final List<ThreadInformations> result = new ArrayList<>(threadInformationsList);
645         Collections.sort(result, new ThreadInformationsComparator());
646         return Collections.unmodifiableList(result);
647     }
648
649     public List<CacheInformations> getCacheInformationsList() {
650         // on trie sur demande (si affichage)
651         final List<CacheInformations> result = new ArrayList<>(cacheInformationsList);
652         Collections.sort(result, new CacheInformationsComparator());
653         return Collections.unmodifiableList(result);
654     }
655
656     public List<JCacheInformations> getJCacheInformationsList() {
657         // on trie sur demande (si affichage)
658         final List<JCacheInformations> result = new ArrayList<>(jcacheInformationsList);
659         Collections.sort(result, new JCacheInformationsComparator());
660         return Collections.unmodifiableList(result);
661     }
662
663     public List<JobInformations> getJobInformationsList() {
664         // on trie sur demande (si affichage)
665         final List<JobInformations> result = new ArrayList<>(jobInformationsList);
666         Collections.sort(result, new JobInformationsComparator());
667         return Collections.unmodifiableList(result);
668     }
669
670     public int getCurrentlyExecutingJobCount() {
671         int result = 0;
672         for (final JobInformations jobInformations : jobInformationsList) {
673             if (jobInformations.isCurrentlyExecuting()) {
674                 result++;
675             }
676         }
677         return result;
678     }
679
680     public List<HsErrPid> getHsErrPidList() {
681         if (hsErrPidList != null) {
682             // on trie sur demande (si affichage)
683             final List<HsErrPid> result = new ArrayList<>(hsErrPidList);
684             Collections.sort(result, new HsErrPidComparator());
685             return Collections.unmodifiableList(result);
686         }
687         return null;
688     }
689
690     public boolean isStackTraceEnabled() {
691         for (final ThreadInformations threadInformations : threadInformationsList) {
692             final List<StackTraceElement> stackTrace = threadInformations.getStackTrace();
693             if (stackTrace != null && !stackTrace.isEmpty()) {
694                 return true;
695             }
696         }
697         return false;
698     }
699
700     public boolean isCacheEnabled() {
701         return cacheInformationsList != null && !cacheInformationsList.isEmpty();
702     }
703
704     public boolean isJCacheEnabled() {
705         return jcacheInformationsList != null && !jcacheInformationsList.isEmpty();
706     }
707
708     public boolean isJobEnabled() {
709         return jobInformationsList != null && !jobInformationsList.isEmpty();
710     }
711
712     public boolean isSpringBeansEnabled() {
713         return springBeanExists;
714     }
715
716     private static boolean isSpringAvailable() {
717         try {
718             Class.forName("org.springframework.context.ApplicationContextAware");
719             return true;
720         } catch (final ClassNotFoundException e) {
721             return false;
722         }
723     }
724
725     /** {@inheritDoc} */
726     @Override
727     public String toString() {
728         return getClass().getSimpleName() + "[pid=" + getPID() + ", host=" + getHost()
729                 + ", javaVersion=" + getJavaVersion() + ", serverInfo=" + getServerInfo() + ']';
730     }
731 }
732