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;
19
20 import java.io.File;
21 import java.io.IOException;
22 import java.lang.management.ManagementFactory;
23 import java.lang.reflect.Method;
24 import java.text.DateFormat;
25 import java.text.SimpleDateFormat;
26 import java.util.Date;
27 import java.util.List;
28 import java.util.Locale;
29
30 import javax.cache.Cache.Entry;
31 import javax.cache.Caching;
32 import javax.cache.spi.CachingProvider;
33 import javax.management.JMException;
34 import javax.management.ObjectName;
35 import javax.management.openmbean.CompositeData;
36 import javax.servlet.http.HttpSession;
37
38 import org.quartz.JobDetail;
39 import org.quartz.Scheduler;
40
41 import net.bull.javamelody.Parameter;
42 import net.bull.javamelody.SessionListener;
43 import net.bull.javamelody.internal.common.I18N;
44 import net.bull.javamelody.internal.common.InputOutput;
45 import net.bull.javamelody.internal.common.LOG;
46 import net.bull.javamelody.internal.common.Parameters;
47 import net.bull.javamelody.internal.web.MailReport;
48 import net.sf.ehcache.Cache;
49 import net.sf.ehcache.CacheManager;
50
51 /**
52  * Énumération des actions possibles dans l'IHM.
53  * @author Emeric Vernat
54  * @author <a href="mailto:davidkarlsen@gmail.com">David J. M. Karlsen (IBM heapdump support)<a>
55  */

56 public enum Action {
57     /** Test d'envoi du rapport pdf par mail. */
58     MAIL_TEST(""),
59
60     /** Réinitialisation d'un compteur non périodique. */
61     CLEAR_COUNTER("http"),
62
63     /** Garbage Collect. */
64     GC("systeminfo"),
65
66     /** Invalidations des sessions http. */
67     INVALIDATE_SESSIONS("systeminfo"),
68
69     /** Invalidation d'une session http. */
70     INVALIDATE_SESSION(""),
71
72     /** Invalidation de la session http courante. */
73     LOGOUT(""),
74
75     /** Heap dump. */
76     HEAP_DUMP("systeminfo"),
77
78     /** Purge le contenu de tous les caches (ie, for ALL_CACHE_MANAGERS {cacheManager.clearAll()}). */
79     CLEAR_CACHES("caches"),
80
81     /** Purge le contenu  d'un cache. */
82     CLEAR_CACHE("caches"),
83
84     /** Purge la clé d'un cache. */
85     CLEAR_CACHE_KEY("caches"),
86
87     /** Purge le contenu de tous les jcaches. */
88     CLEAR_JCACHES("caches"),
89
90     /** Purge le contenu  d'un jcache. */
91     CLEAR_JCACHE("caches"),
92
93     /** Purge la clé d'un jcache. */
94     CLEAR_JCACHE_KEY("caches"),
95
96     /** Tue un thread java. */
97     KILL_THREAD("threads"),
98
99     /** Envoi un signal interrupt à un thread java. */
100     SEND_THREAD_INTERRUPT("threads"),
101
102     /** Met un job quartz en pause. */
103     PAUSE_JOB("jobs"),
104
105     /** Enlève la pause d'un job quartz. */
106     RESUME_JOB("jobs"),
107
108     /** Réinitialisation des hotspots. */
109     CLEAR_HOTSPOTS(""),
110
111     /** Purge les fichiers .rrd et .ser.gz obsolètes. */
112     PURGE_OBSOLETE_FILES("bottom");
113
114     /**
115      * Booléen selon que l'action 'Garbage collector' est possible.
116      */

117     public static final boolean GC_ENABLED = !ManagementFactory.getRuntimeMXBean()
118             .getInputArguments().contains("-XX:+DisableExplicitGC");
119
120     static final String JAVA_VENDOR = System.getProperty("java.vendor");
121     static final String JAVA_VM_VENDOR = System.getProperty("java.vm.vendor");
122
123     private static final String ALL = "all";
124
125     /**
126      * Nom du contexte dans lequel est exécutée l'action
127      * (servira dans l'url pour replacer la page html sur l'anchor de même nom)
128      */

129     private final String contextName;
130
131     Action(String contextName) {
132         this.contextName = contextName;
133     }
134
135     public String getContextName(String counterName) {
136         if (this == CLEAR_COUNTER && !ALL.equalsIgnoreCase(counterName)) {
137             return counterName;
138         }
139         return contextName;
140     }
141
142     /**
143      * Convertit le code d'une action en énumération de l'action.
144      * @param action String
145      * @return Action
146      */

147     public static Action valueOfIgnoreCase(String action) {
148         return valueOf(action.toUpperCase(Locale.ENGLISH).trim());
149     }
150
151     /**
152      * Vérifie que le paramètre pour activer les actions systèmes est positionné.
153      */

154     public static void checkSystemActionsEnabled() {
155         if (!Parameters.isSystemActionsEnabled()) {
156             throw new IllegalStateException(I18N.getString("Actions_non_activees"));
157         }
158     }
159
160     // méthode conservée pour compatibilité ascendante, notamment dans Jenkins
161     public String execute(Collector collector, CollectorServer collectorServer, String counterName,
162             String sessionId, String threadId, String jobId, String cacheId) throws IOException {
163         return execute(collector, collectorServer, null, counterName, sessionId, threadId, jobId,
164                 cacheId, null);
165     }
166
167     // CHECKSTYLE:OFF
168     // since 1.49
169     String execute(Collector collector, CollectorServer collectorServer, HttpSession currentSession,
170             String counterName, String sessionId, String threadId, String jobId, String cacheId)
171             throws IOException {
172         // CHECKSTYLE:ON
173         return execute(collector, collectorServer, currentSession, counterName, sessionId, threadId,
174                 jobId, cacheId, null);
175     }
176
177     /**
178      * Exécute l'action.
179      * @param collector Collector pour une réinitialisation et test de mail
180      * @param collectorServer Serveur de collecte pour test de mail (null s'il n'y en a pas)
181      * @param currentSession session http de l'utilisateur exécutant l'action (null sinon)
182      * @param counterName Nom du compteur pour une réinitialisation
183      * @param sessionId Identifiant de session pour invalidation (null sinon)
184      * @param threadId Identifiant du thread sous la forme pid_ip_id
185      * @param jobId Identifiant du job sous la forme pid_ip_id
186      * @param cacheId Identifiant du cache à vider
187      * @param cacheKey Identifiant d'une clé de cache à vider
188      * @return Message de résultat
189      * @throws IOException e
190      * @since 1.66
191      */

192     // CHECKSTYLE:OFF
193     public String execute(Collector collector, CollectorServer collectorServer, // NOPMD
194             HttpSession currentSession, String counterName, String sessionId, String threadId,
195             String jobId, String cacheId, String cacheKey) throws IOException {
196         // CHECKSTYLE:ON
197         final String messageForReport;
198         switch (this) {
199         case CLEAR_COUNTER:
200             assert collector != null;
201             assert counterName != null;
202             messageForReport = clearCounter(collector, counterName);
203             break;
204         case MAIL_TEST:
205             assert collector != null;
206             messageForReport = mailTest(collector, collectorServer);
207             break;
208         case GC:
209             if (GC_ENABLED) {
210                 // garbage collector
211                 final long kbFreed = gc();
212                 final long stillUsed = (Runtime.getRuntime().totalMemory()
213                         - Runtime.getRuntime().freeMemory()) / 1024;
214                 messageForReport = I18N.getFormattedString("ramasse_miette_execute", kbFreed,
215                         stillUsed);
216             } else {
217                 messageForReport = I18N.getString("ramasse_miette_desactive");
218             }
219             break;
220         case HEAP_DUMP:
221             if (JAVA_VENDOR.contains("IBM") || JAVA_VM_VENDOR.contains("OpenJ9")) {
222                 ibmHeapDump();
223                 messageForReport = I18N.getString("heap_dump_genere_ibm");
224             } else {
225                 // heap dump à générer dans le répertoire temporaire sur le serveur
226                 // avec un suffixe contenant le host, la date et l'heure et avec une extension hprof
227                 // (utiliser jvisualvm du jdk ou MAT d'eclipse en standalone ou en plugin)
228                 final File heapDump = heapDump();
229                 final File zipFile = new File(heapDump.getParentFile(),
230                         heapDump.getName() + ".zip");
231                 InputOutput.zipFile(heapDump, zipFile);
232                 InputOutput.deleteFile(heapDump);
233                 String message = "";
234                 if (Parameter.HEAP_DUMP_S3_BUCKETNAME.getValue() != null) {
235                     try {
236                         S3.upload(zipFile, Parameter.HEAP_DUMP_S3_BUCKETNAME.getValue());
237                         message = I18N.getFormattedString("heap_dump_uploaded_to_s3",
238                                 zipFile.getName()) + ' ';
239                     } catch (final IOException e) {
240                         message = "Failed to upload heap dump to S3 - " + e.getMessage() + '\n';
241                     }
242                 }
243                 final String path = zipFile.getPath();
244                 messageForReport = message
245                         + I18N.getFormattedString("heap_dump_genere", path.replace('\\', '/'));
246             }
247             break;
248         case INVALIDATE_SESSIONS:
249             // invalidation des sessions http
250             SessionListener.invalidateAllSessionsExceptCurrentSession(currentSession);
251             messageForReport = I18N.getString("sessions_http_invalidees");
252             break;
253         case INVALIDATE_SESSION:
254             // invalidation d'une session http
255             assert sessionId != null;
256             SessionListener.invalidateSession(sessionId);
257             messageForReport = I18N.getString("session_http_invalidee");
258             break;
259         case LOGOUT:
260             // invalidation de la session http courante
261             if (currentSession != null) {
262                 SessionListener.invalidateSession(currentSession.getId());
263             }
264             messageForReport = I18N.getString("logged_out");
265             break;
266         case CLEAR_CACHES:
267             clearCaches();
268             messageForReport = I18N.getString("caches_purges");
269             break;
270         case CLEAR_CACHE:
271             clearCache(cacheId);
272             messageForReport = I18N.getFormattedString("cache_purge", cacheId);
273             break;
274         case CLEAR_CACHE_KEY:
275             clearCacheKey(cacheId, cacheKey);
276             messageForReport = I18N.getFormattedString("cache_key_purge", cacheId, cacheKey);
277             break;
278         case CLEAR_JCACHES:
279             clearJCaches();
280             messageForReport = I18N.getString("caches_purges");
281             break;
282         case CLEAR_JCACHE:
283             clearJCache(cacheId);
284             messageForReport = I18N.getFormattedString("cache_purge", cacheId);
285             break;
286         case CLEAR_JCACHE_KEY:
287             clearJCacheKey(cacheId, cacheKey);
288             messageForReport = I18N.getFormattedString("cache_key_purge", cacheId, cacheKey);
289             break;
290         case KILL_THREAD:
291             assert threadId != null;
292             messageForReport = killThread(threadId);
293             break;
294         case SEND_THREAD_INTERRUPT:
295             assert threadId != null;
296             messageForReport = sendThreadInterrupt(threadId);
297             break;
298         case PAUSE_JOB:
299             assert jobId != null;
300             messageForReport = pauseJob(jobId);
301             break;
302         case RESUME_JOB:
303             assert jobId != null;
304             messageForReport = resumeJob(jobId);
305             break;
306         case CLEAR_HOTSPOTS:
307             assert collector.getSamplingProfiler() != null;
308             collector.getSamplingProfiler().clear();
309             messageForReport = I18N.getString("hotspots_cleared");
310             break;
311         case PURGE_OBSOLETE_FILES:
312             assert collector != null;
313             collector.deleteObsoleteFiles();
314             messageForReport = I18N.getString("fichiers_obsoletes_purges") + '\n'
315                     + I18N.getString("Usage_disque") + ": "
316                     + (collector.getDiskUsage() / 1024 / 1024 + 1) + ' ' + I18N.getString("Mo");
317             break;
318         default:
319             throw new IllegalStateException(toString());
320         }
321         if (messageForReport != null) {
322             // log pour information en debug
323             LOG.debug("Action '" + this + "' executed. Result: "
324                     + messageForReport.replace('\n', ' '));
325         }
326         return messageForReport;
327     }
328
329     private String clearCounter(Collector collector, String counterName) {
330         final String messageForReport;
331         if (ALL.equalsIgnoreCase(counterName)) {
332             for (final Counter counter : collector.getCounters()) {
333                 collector.clearCounter(counter.getName());
334             }
335             messageForReport = I18N.getFormattedString("Toutes_statistiques_reinitialisees",
336                     counterName);
337         } else {
338             // l'action Réinitialiser a été appelée pour un compteur
339             collector.clearCounter(counterName);
340             messageForReport = I18N.getFormattedString("Statistiques_reinitialisees", counterName);
341         }
342         return messageForReport;
343     }
344
345     private String mailTest(Collector collector, CollectorServer collectorServer) {
346         // note: a priori, inutile de traduire cela
347         if (!Parameters.isPdfEnabled()) {
348             throw new IllegalStateException("itext classes not found: add the itext dependency");
349         }
350         if (Parameter.MAIL_SESSION.getValue() == null) {
351             throw new IllegalStateException(
352                     "mail-session has no value: add the mail-session parameter");
353         }
354         if (Parameter.ADMIN_EMAILS.getValue() == null) {
355             throw new IllegalStateException(
356                     "admin-emails has no value: add the admin-emails parameter");
357         }
358         try {
359             if (collectorServer == null) {
360                 // serveur local
361                 new MailReport().sendReportMailForLocalServer(collector, Period.JOUR);
362             } else {
363                 // serveur de collecte
364                 new MailReport().sendReportMail(collector, true, collectorServer
365                         .getJavaInformationsByApplication(collector.getApplication()), Period.JOUR);
366             }
367         } catch (final Exception e) {
368             throw new RuntimeException(e); // NOPMD
369         }
370         return "Mail sent with pdf report for the day to admins";
371     }
372
373     private File heapDump() {
374         try {
375             final ObjectName objectName = new ObjectName(
376                     "com.sun.management:type=HotSpotDiagnostic");
377             final CompositeData vmOption = (CompositeData) MBeansAccessor.invoke(objectName,
378                     "getVMOption"new Object[] { "HeapDumpPath" }, new Class[] { String.class });
379             final String heapDumpPath;
380             if (vmOption == null) {
381                 heapDumpPath = null;
382             } else {
383                 heapDumpPath = (String) vmOption.get("value");
384             }
385             final String path;
386             if (heapDumpPath == null || heapDumpPath.isEmpty()) {
387                 path = Parameters.TEMPORARY_DIRECTORY.getPath();
388             } else {
389                 // -XX:HeapDumpPath=/tmp par exemple a été spécifié comme paramètre de VM.
390                 // Dans ce cas, on prend en compte ce paramètre "standard" de la JVM Hotspot
391                 final File file = new File(heapDumpPath);
392                 if (file.exists()) {
393                     if (file.isDirectory()) {
394                         path = heapDumpPath;
395                     } else {
396                         path = file.getParent();
397                     }
398                 } else {
399                     if (!file.mkdirs()) {
400                         throw new IllegalStateException("Can't create directory " + file.getPath());
401                     }
402                     path = heapDumpPath;
403                 }
404             }
405             final DateFormat dateFormat = new SimpleDateFormat("yyyyMMdd_HHmmss",
406                     Locale.getDefault());
407             final File heapDumpFile = new File(path, "heapdump-" + Parameters.getHostName() + '-'
408                     + PID.getPID() + '-' + dateFormat.format(new Date()) + ".hprof");
409             if (heapDumpFile.exists()) {
410                 try {
411                     // si le fichier existe déjà, un heap dump a déjà été généré dans la même seconde
412                     // donc on attends 1 seconde pour créer le fichier avec un nom différent
413                     Thread.sleep(1000);
414                 } catch (final InterruptedException e) {
415                     throw new IllegalStateException(e);
416                 }
417                 return heapDump();
418             }
419             final boolean gcBeforeHeapDump = true;
420             MBeansAccessor.invoke(objectName, "dumpHeap",
421                     new Object[] { heapDumpFile.getPath(), gcBeforeHeapDump },
422                     new Class[] { String.classboolean.class });
423             return heapDumpFile;
424         } catch (final JMException e) {
425             throw new IllegalStateException(e);
426         }
427     }
428
429     private void ibmHeapDump() {
430         try {
431             final Class<?> dumpClass = getClass().getClassLoader().loadClass("com.ibm.jvm.Dump"); // NOPMD
432             final Class<?>[] argTypes = null;
433             final Method dump = dumpClass.getMethod("HeapDump", argTypes);
434             final Object[] args = null;
435             dump.invoke(null, args);
436         } catch (final Exception e) {
437             throw new IllegalStateException(e);
438         }
439     }
440
441     // cette méthode doit s'appeler "gc" pour que findbugs ne fasse pas de warning
442     @SuppressWarnings("all")
443     private long gc() {
444         final long before = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
445         Runtime.getRuntime().gc();
446         final long after = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
447         return (before - after) / 1024;
448     }
449
450     private void clearCaches() {
451         final List<CacheManager> allCacheManagers = CacheManager.ALL_CACHE_MANAGERS;
452         for (final CacheManager cacheManager : allCacheManagers) {
453             cacheManager.clearAll();
454         }
455     }
456
457     private void clearCache(String cacheId) {
458         final List<CacheManager> allCacheManagers = CacheManager.ALL_CACHE_MANAGERS;
459         for (final CacheManager cacheManager : allCacheManagers) {
460             final Cache cache = cacheManager.getCache(cacheId);
461             if (cache != null) {
462                 cache.removeAll();
463             }
464         }
465     }
466
467     private void clearCacheKey(String cacheId, String cacheKey) {
468         final List<CacheManager> allCacheManagers = CacheManager.ALL_CACHE_MANAGERS;
469         for (final CacheManager cacheManager : allCacheManagers) {
470             final Cache cache = cacheManager.getCache(cacheId);
471             if (cache != null) {
472                 final boolean removed = cache.remove(cacheKey);
473                 if (!removed) {
474                     // if keys are not Strings, we have to find the initial key
475                     for (final Object key : cache.getKeys()) {
476                         if (key != null && key.toString().equals(cacheKey)) {
477                             cache.remove(key);
478                             break;
479                         }
480                     }
481                 }
482             }
483         }
484     }
485
486     private void clearJCaches() {
487         for (final CachingProvider cachingProvider : Caching.getCachingProviders()) {
488             final javax.cache.CacheManager cacheManager = cachingProvider.getCacheManager();
489             for (final String cacheName : cacheManager.getCacheNames()) {
490                 cacheManager.getCache(cacheName).clear();
491             }
492         }
493     }
494
495     private void clearJCache(String cacheId) {
496         for (final CachingProvider cachingProvider : Caching.getCachingProviders()) {
497             final javax.cache.CacheManager cacheManager = cachingProvider.getCacheManager();
498             for (final String cacheName : cacheManager.getCacheNames()) {
499                 if (cacheName.equals(cacheId)) {
500                     // getCache may never return null
501                     cacheManager.getCache(cacheId).clear();
502                     break;
503                 }
504             }
505         }
506     }
507
508     private void clearJCacheKey(String cacheId, String cacheKey) {
509         for (final CachingProvider cachingProvider : Caching.getCachingProviders()) {
510             final javax.cache.CacheManager cacheManager = cachingProvider.getCacheManager();
511             for (final String cacheName : cacheManager.getCacheNames()) {
512                 if (cacheName.equals(cacheId)) {
513                     // getCache may never return null
514                     final javax.cache.Cache<Object, Object> cache = cacheManager.getCache(cacheId);
515                     final boolean removed = cache.remove(cacheKey);
516                     if (!removed) {
517                         // if keys are not Strings, we have to find the initial key
518                         for (final Entry<Object, Object> entry : cache) {
519                             final Object key = entry.getKey();
520                             if (key != null && key.toString().equals(cacheKey)) {
521                                 cache.remove(key);
522                                 break;
523                             }
524                         }
525                     }
526                 }
527             }
528         }
529     }
530
531     private String killThread(String globalThreadId) {
532         final Long threadId = getThreadIdFromGlobalThreadIdIfSameJvm(globalThreadId);
533         if (threadId != null) {
534             final List<Thread> threads = JavaInformations.getThreadsFromThreadGroups();
535             for (final Thread thread : threads) {
536                 if (thread.getId() == threadId.longValue()) {
537                     if (thread.getName().startsWith("javamelody")) {
538                         return "I will not kill myself";
539                     }
540                     stopThread(thread);
541                     return I18N.getFormattedString("Thread_tue", thread.getName());
542                 }
543             }
544             return I18N.getString("Thread_non_trouve");
545         }
546
547         // cette action ne concernait pas cette JVM, donc on ne fait rien
548         return null;
549     }
550
551     @SuppressWarnings("deprecation")
552     private void stopThread(Thread thread) {
553         // I know that it is unsafe and the user has been warned
554         thread.stop();
555     }
556
557     private String sendThreadInterrupt(String globalThreadId) {
558         final Long threadId = getThreadIdFromGlobalThreadIdIfSameJvm(globalThreadId);
559         if (threadId != null) {
560             final List<Thread> threads = JavaInformations.getThreadsFromThreadGroups();
561             for (final Thread thread : threads) {
562                 if (thread.getId() == threadId.longValue()) {
563                     if (thread.getName().startsWith("javamelody")) {
564                         return "I will not interrupt myself";
565                     }
566                     thread.interrupt();
567                     return I18N.getFormattedString("thread_interrupt_sent", thread.getName());
568                 }
569             }
570             return I18N.getString("Thread_non_trouve");
571         }
572
573         // cette action ne concernait pas cette JVM, donc on ne fait rien
574         return null;
575     }
576
577     private Long getThreadIdFromGlobalThreadIdIfSameJvm(String globalThreadId) {
578         final String[] values = globalThreadId.split("_");
579         if (values.length != 3) {
580             throw new IllegalArgumentException(globalThreadId);
581         }
582         // rq : la syntaxe vérifiée ici doit être conforme à ThreadInformations.buildGlobalThreadId
583         if (values[0].equals(PID.getPID()) && values[1].equals(Parameters.getHostAddress())) {
584             return Long.valueOf(values[2]);
585         }
586         return null;
587     }
588
589     private String pauseJob(String jobId) {
590         if (ALL.equalsIgnoreCase(jobId)) {
591             pauseAllJobs();
592             return I18N.getString("all_jobs_paused");
593         }
594
595         final String[] values = jobId.split("_");
596         if (values.length != 3) {
597             throw new IllegalArgumentException(jobId);
598         }
599         // rq : la syntaxe vérifiée ici doit être conforme à JobInformations.buildGlobalJobId
600         if (values[0].equals(PID.getPID()) && values[1].equals(Parameters.getHostAddress())) {
601             if (pauseJobById(Integer.parseInt(values[2]))) {
602                 return I18N.getString("job_paused");
603             }
604             return I18N.getString("job_notfound");
605         }
606
607         // cette action ne concernait pas cette JVM, donc on ne fait rien
608         return null;
609     }
610
611     private boolean pauseJobById(int myJobId) {
612         try {
613             final QuartzAdapter quartzAdapter = QuartzAdapter.getSingleton();
614             for (final Scheduler scheduler : JobInformations.getAllSchedulers()) {
615                 for (final JobDetail jobDetail : quartzAdapter.getAllJobsOfScheduler(scheduler)) {
616                     if (quartzAdapter.getJobFullName(jobDetail).hashCode() == myJobId) {
617                         quartzAdapter.pauseJob(jobDetail, scheduler);
618                         return true;
619                     }
620                 }
621             }
622             return false;
623         } catch (final Exception e) {
624             throw new IllegalStateException(e);
625         }
626     }
627
628     private void pauseAllJobs() {
629         try {
630             for (final Scheduler scheduler : JobInformations.getAllSchedulers()) {
631                 scheduler.pauseAll();
632             }
633         } catch (final Exception e) {
634             throw new IllegalStateException(e);
635         }
636     }
637
638     private String resumeJob(String jobId) {
639         if (ALL.equalsIgnoreCase(jobId)) {
640             resumeAllJobs();
641             return I18N.getString("all_jobs_resumed");
642         }
643         final String[] values = jobId.split("_");
644         if (values.length != 3) {
645             throw new IllegalArgumentException(jobId);
646         }
647         // rq : la syntaxe vérifiée ici doit être conforme à JobInformations.buildGlobalJobId
648         if (values[0].equals(PID.getPID()) && values[1].equals(Parameters.getHostAddress())) {
649             if (resumeJobById(Integer.parseInt(values[2]))) {
650                 return I18N.getString("job_resumed");
651             }
652             return I18N.getString("job_notfound");
653         }
654
655         // cette action ne concernait pas cette JVM, donc on ne fait rien
656         return null;
657     }
658
659     private boolean resumeJobById(int myJobId) {
660         try {
661             final QuartzAdapter quartzAdapter = QuartzAdapter.getSingleton();
662             for (final Scheduler scheduler : JobInformations.getAllSchedulers()) {
663                 for (final JobDetail jobDetail : quartzAdapter.getAllJobsOfScheduler(scheduler)) {
664                     if (quartzAdapter.getJobFullName(jobDetail).hashCode() == myJobId) {
665                         quartzAdapter.resumeJob(jobDetail, scheduler);
666                         return true;
667                     }
668                 }
669             }
670             return false;
671         } catch (final Exception e) {
672             throw new IllegalStateException(e);
673         }
674     }
675
676     private void resumeAllJobs() {
677         try {
678             for (final Scheduler scheduler : JobInformations.getAllSchedulers()) {
679                 scheduler.resumeAll();
680             }
681         } catch (final Exception e) {
682             throw new IllegalStateException(e);
683         }
684     }
685 }
686