KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > sun > jmx > examples > scandir > ScanManager


1 /*
2  * ScanManager.java
3  *
4  * Created on July 10, 2006, 2:17 PM
5  *
6  * @(#)ScanManager.java 1.2 06/08/02
7  *
8  * Copyright (c) 2006 Sun Microsystems, Inc. All Rights Reserved.
9  *
10  * Redistribution and use in source and binary forms, with or without
11  * modification, are permitted provided that the following conditions are met:
12  *
13  * -Redistribution of source code must retain the above copyright notice, this
14  * list of conditions and the following disclaimer.
15  *
16  * -Redistribution in binary form must reproduce the above copyright notice,
17  * this list of conditions and the following disclaimer in the documentation
18  * and/or other materials provided with the distribution.
19  *
20  * Neither the name of Sun Microsystems, Inc. or the names of contributors may
21  * be used to endorse or promote products derived from this software without
22  * specific prior written permission.
23  *
24  * This software is provided "AS IS," without a warranty of any kind. ALL
25  * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING
26  * ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE
27  * OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN")
28  * AND ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE
29  * AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS
30  * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST
31  * REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL,
32  * INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY
33  * OF LIABILITY, ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE,
34  * EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
35  *
36  * You acknowledge that this software is not designed, licensed or intended
37  * for use in the design, construction, operation or maintenance of any
38  * nuclear facility.
39  */

40
41 package com.sun.jmx.examples.scandir;
42
43 import static com.sun.jmx.examples.scandir.ScanManagerMXBean.ScanState.*;
44 import com.sun.jmx.examples.scandir.ScanManagerMXBean.ScanState;
45 import com.sun.jmx.examples.scandir.config.DirectoryScannerConfig;
46 import com.sun.jmx.examples.scandir.config.ScanManagerConfig;
47 import java.io.File JavaDoc;
48
49 import java.io.IOException JavaDoc;
50 import java.lang.management.ManagementFactory JavaDoc;
51 import java.util.ArrayList JavaDoc;
52 import java.util.Collections JavaDoc;
53 import java.util.EnumSet JavaDoc;
54 import java.util.HashMap JavaDoc;
55 import java.util.Map JavaDoc;
56 import java.util.Map.Entry;
57 import java.util.Timer JavaDoc;
58 import java.util.TimerTask JavaDoc;
59 import java.util.concurrent.BlockingQueue JavaDoc;
60 import java.util.concurrent.ConcurrentHashMap JavaDoc;
61 import java.util.concurrent.ConcurrentLinkedQueue JavaDoc;
62 import java.util.concurrent.LinkedBlockingQueue JavaDoc;
63 import java.util.concurrent.Semaphore JavaDoc;
64 import java.util.concurrent.TimeUnit JavaDoc;
65 import java.util.logging.Level JavaDoc;
66 import java.util.logging.Logger JavaDoc;
67 import javax.management.AttributeChangeNotification JavaDoc;
68 import javax.management.InstanceNotFoundException JavaDoc;
69 import javax.management.JMException JavaDoc;
70 import javax.management.JMX JavaDoc;
71 import javax.management.ListenerNotFoundException JavaDoc;
72 import javax.management.MBeanNotificationInfo JavaDoc;
73 import javax.management.MBeanRegistration JavaDoc;
74 import javax.management.MBeanServer JavaDoc;
75 import javax.management.MBeanServerConnection JavaDoc;
76 import javax.management.MalformedObjectNameException JavaDoc;
77 import javax.management.Notification JavaDoc;
78 import javax.management.NotificationBroadcasterSupport JavaDoc;
79 import javax.management.NotificationEmitter JavaDoc;
80 import javax.management.NotificationFilter JavaDoc;
81 import javax.management.NotificationListener JavaDoc;
82 import javax.management.ObjectInstance JavaDoc;
83 import javax.management.ObjectName JavaDoc;
84
85 /**
86  * <p>
87  * The <code>ScanManager</code> is responsible for applying a configuration,
88  * starting and scheduling directory scans, and reporting application state.
89  * </p>
90  * <p>
91  * The ScanManager MBean is a singleton MBean which controls
92  * scan session. The ScanManager name is defined by
93  * {@link #SCAN_MANAGER_NAME ScanManager.SCAN_MANAGER_NAME}.
94  * </p>
95  * <p>
96  * The <code>ScanManager</code> MBean is the entry point of the <i>scandir</i>
97  * application management interface. It is from this MBean that all other MBeans
98  * will be created and registered.
99  * </p>
100  *
101  * @author Sun Microsystems, 2006 - All rights reserved.
102  */

103 public class ScanManager implements ScanManagerMXBean,
104         NotificationEmitter JavaDoc, MBeanRegistration JavaDoc {
105
106     /**
107      * A logger for this class.
108      **/

109     private static final Logger JavaDoc LOG =
110             Logger.getLogger(ScanManager.class.getName());
111
112     /**
113      * The name of the ScanManager singleton MBean.
114      **/

115     public final static ObjectName JavaDoc SCAN_MANAGER_NAME =
116             makeSingletonName(ScanManagerMXBean.class);
117
118     /**
119      * Sequence number used for sending notifications. We use this
120      * sequence number throughout the application.
121      **/

122     private static long seqNumber=0;
123
124     /**
125      * The NotificationBroadcasterSupport object used to handle
126      * listener registration.
127      **/

128     private final NotificationBroadcasterSupport JavaDoc broadcaster;
129
130     /**
131      * The MBeanServer in which this MBean is registered. We obtain
132      * this reference by implementing the {@link MBeanRegistration}
133      * interface.
134      **/

135     private volatile MBeanServer JavaDoc mbeanServer;
136
137     /**
138      * A queue of pending notifications we are about to send.
139      * We're using a BlockingQueue in order to avoid sending
140      * notifications from within a synchronized block.
141      **/

142     private final BlockingQueue JavaDoc<Notification JavaDoc> pendingNotifs;
143
144     /**
145      * The state of the scan session.
146      **/

147     private volatile ScanState state = STOPPED;
148
149     /**
150      * The list of DirectoryScannerMBean that are run by a scan session.
151      **/

152     private final Map JavaDoc<ObjectName JavaDoc,DirectoryScannerMXBean> scanmap;
153
154     /**
155      * The list of ScanDirConfigMXBean that were created by this MBean.
156      **/

157     private final Map JavaDoc<ObjectName JavaDoc, ScanDirConfigMXBean> configmap;
158     
159     // The ResultLogManager for this application.
160
private final ResultLogManager log;
161
162     /**
163      * We use a semaphore to ensure proper sequencing of exclusive
164      * action. The logic we have implemented is to fail - rather
165      * than block, if an exclusive action is already in progress.
166      **/

167     private final Semaphore JavaDoc sequencer = new Semaphore JavaDoc(1);
168
169     // A proxy to the current ScanDirConfigMXBean which holds the current
170
// configuration data.
171
//
172
private volatile ScanDirConfigMXBean config = null;
173
174     // Avoid to write parameters twices when creating a new ConcurrentHashMap.
175
//
176
private static <K, V> Map JavaDoc<K, V> newConcurrentHashMap() {
177         return new ConcurrentHashMap JavaDoc<K, V>();
178     }
179     
180     // Avoid to write parameters twices when creating a new HashMap.
181
//
182
private static <K, V> Map JavaDoc<K, V> newHashMap() {
183         return new HashMap JavaDoc<K, V>();
184     }
185     
186     /**
187      * Creates a default singleton ObjectName for a given class.
188      * @param clazz The interface class of the MBean for which we want to obtain
189      * a default singleton name, or its implementation class.
190      * Give one or the other depending on what you wish to see in
191      * the value of the key {@code type=}.
192      * @return A default singleton name for a singleton MBean class.
193      * @throws IllegalArgumentException if the name can't be created
194      * for some unfathomable reason (e.g. an unexpected
195      * exception was raised).
196      **/

197     public final static ObjectName JavaDoc makeSingletonName(Class JavaDoc clazz) {
198         try {
199             final Package JavaDoc p = clazz.getPackage();
200             final String JavaDoc packageName = (p==null)?null:p.getName();
201             final String JavaDoc className = clazz.getSimpleName();
202             final String JavaDoc domain;
203             if (packageName == null || packageName.length()==0) {
204                 // We use a reference to ScanDirAgent.class to ease
205
// to keep track of possible class renaming.
206
domain = ScanDirAgent.class.getSimpleName();
207             } else {
208                 domain = packageName;
209             }
210             final ObjectName JavaDoc name = new ObjectName JavaDoc(domain,"type",className);
211             return name;
212         } catch (Exception JavaDoc x) {
213             final IllegalArgumentException JavaDoc iae =
214                     new IllegalArgumentException JavaDoc(String.valueOf(clazz),x);
215             throw iae;
216         }
217     }
218
219     /**
220      * Creates a default ObjectName with keys <code>type=</code> and
221      * <code>name=</code> for an instance of a given MBean interface class.
222      * @param clazz The interface class of the MBean for which we want to obtain
223      * a default name, or its implementation class.
224      * Give one or the other depending on what you wish to see in
225      * the value of the key {@code type=}.
226      * @param name The value of the <code>name=</code> key.
227      * @return A default name for an instance of the given MBean interface class.
228      * @throws IllegalArgumentException if the name can't be created.
229      * (e.g. an unexpected exception was raised).
230      **/

231     public static final ObjectName JavaDoc makeMBeanName(Class JavaDoc clazz, String JavaDoc name) {
232         try {
233             return ObjectName.
234                 getInstance(makeSingletonName(clazz)
235                         .toString()+",name="+name);
236         } catch (MalformedObjectNameException JavaDoc x) {
237             final IllegalArgumentException JavaDoc iae =
238                     new IllegalArgumentException JavaDoc(String.valueOf(name),x);
239             throw iae;
240         }
241     }
242
243     /**
244      * Return the ObjectName for a DirectoryScannerMXBean of that name.
245      * This is {@code makeMBeanName(DirectoryScannerMXBean.class,name)}.
246      * @param name The value of the <code>name=</code> key.
247      * @return the ObjectName for a DirectoryScannerMXBean of that name.
248      */

249     public static final ObjectName JavaDoc makeDirectoryScannerName(String JavaDoc name) {
250         return makeMBeanName(DirectoryScannerMXBean.class,name);
251     }
252
253     /**
254      * Return the ObjectName for a {@code ScanDirConfigMXBean} of that name.
255      * This is {@code makeMBeanName(ScanDirConfigMXBean.class,name)}.
256      * @param name The value of the <code>name=</code> key.
257      * @return the ObjectName for a {@code ScanDirConfigMXBean} of that name.
258      */

259     public static final ObjectName JavaDoc makeScanDirConfigName(String JavaDoc name) {
260         return makeMBeanName(ScanDirConfigMXBean.class,name);
261     }
262
263     /**
264      * Create and register a new singleton instance of the ScanManager
265      * MBean in the given {@link MBeanServerConnection}.
266      * @param mbs The MBeanServer in which the new singleton instance
267      * should be created.
268      * @throws JMException The MBeanServer connection raised an exception
269      * while trying to instantiate and register the singleton MBean
270      * instance.
271      * @throws IOException There was a connection problem while trying to
272      * communicate with the underlying MBeanServer.
273      * @return A proxy for the registered MBean.
274      **/

275     public static ScanManagerMXBean register(MBeanServerConnection JavaDoc mbs)
276         throws IOException JavaDoc, JMException JavaDoc {
277         final ObjectInstance JavaDoc moi =
278                 mbs.createMBean(ScanManager.class.getName(),SCAN_MANAGER_NAME);
279         final ScanManagerMXBean proxy =
280                 JMX.newMXBeanProxy(mbs,moi.getObjectName(),
281                                   ScanManagerMXBean.class,true);
282         return proxy;
283     }
284
285     /**
286      * Creates a new {@code ScanManagerMXBean} proxy over the given
287      * {@code MBeanServerConnection}. Does not check whether a
288      * {@code ScanManagerMXBean}
289      * is actually registered in that {@code MBeanServerConnection}.
290      * @return a new {@code ScanManagerMXBean} proxy.
291      * @param mbs The {@code MBeanServerConnection} which holds the
292      * {@code ScanManagerMXBean} to proxy.
293      */

294     public static ScanManagerMXBean
295             newSingletonProxy(MBeanServerConnection JavaDoc mbs) {
296         final ScanManagerMXBean proxy =
297                 JMX.newMXBeanProxy(mbs,SCAN_MANAGER_NAME,
298                                   ScanManagerMXBean.class,true);
299         return proxy;
300     }
301
302     /**
303      * Creates a new {@code ScanManagerMXBean} proxy over the platform
304      * {@code MBeanServer}. This is equivalent to
305      * {@code newSingletonProxy(ManagementFactory.getPlatformMBeanServer())}.
306      * @return a new {@code ScanManagerMXBean} proxy.
307      **/

308     public static ScanManagerMXBean newSingletonProxy() {
309         return newSingletonProxy(ManagementFactory.getPlatformMBeanServer());
310     }
311
312     /**
313      * Create and register a new singleton instance of the ScanManager
314      * MBean in the given {@link MBeanServerConnection}.
315      * @throws JMException The MBeanServer connection raised an exception
316      * while trying to instantiate and register the singleton MBean
317      * instance.
318      * @throws IOException There was a connection problem while trying to
319      * communicate with the underlying MBeanServer.
320      * @return A proxy for the registered MBean.
321      **/

322     public static ScanManagerMXBean register()
323         throws IOException JavaDoc, JMException JavaDoc {
324         final MBeanServer JavaDoc mbs = ManagementFactory.getPlatformMBeanServer();
325         return register(mbs);
326     }
327
328     /**
329      * Create a new ScanManager MBean
330      **/

331     public ScanManager() {
332         broadcaster = new NotificationBroadcasterSupport JavaDoc();
333         pendingNotifs = new LinkedBlockingQueue JavaDoc<Notification JavaDoc>(100);
334         scanmap = newConcurrentHashMap();
335         configmap = newConcurrentHashMap();
336         log = new ResultLogManager();
337     }
338
339
340     // Creates a new DirectoryScannerMXBean, from the given configuration data.
341
DirectoryScannerMXBean createDirectoryScanner(DirectoryScannerConfig config) {
342             return new DirectoryScanner(config,log);
343     }
344
345     // Applies a configuration.
346
// throws IllegalStateException if lock can't be acquired.
347
// Unregisters all existing directory scanners, the create and registers
348
// new directory scanners according to the given config.
349
// Then pushes the log config to the result log manager.
350
//
351
private void applyConfiguration(ScanManagerConfig bean)
352         throws IOException JavaDoc, JMException JavaDoc {
353         if (bean == null) return;
354         if (!sequencer.tryAcquire()) {
355             throw new IllegalStateException JavaDoc("Can't acquire lock");
356         }
357         try {
358             unregisterScanners();
359             final DirectoryScannerConfig[] scans = bean.getScanList();
360             if (scans == null) return;
361             for (DirectoryScannerConfig scan : scans) {
362                 addDirectoryScanner(scan);
363             }
364             log.setConfig(bean.getInitialResultLogConfig());
365         } finally {
366             sequencer.release();
367         }
368     }
369
370     // See ScanManagerMXBean
371
public void applyConfiguration(boolean fromMemory)
372         throws IOException JavaDoc, JMException JavaDoc {
373         if (fromMemory == false) config.load();
374         applyConfiguration(config.getConfiguration());
375     }
376
377     // See ScanManagerMXBean
378
public void applyCurrentResultLogConfig(boolean toMemory)
379         throws IOException JavaDoc, JMException JavaDoc {
380         final ScanManagerConfig bean = config.getConfiguration();
381         bean.setInitialResultLogConfig(log.getConfig());
382         config.setConfiguration(bean);
383         if (toMemory==false) config.save();
384     }
385
386     // See ScanManagerMXBean
387
public void setConfigurationMBean(ScanDirConfigMXBean config) {
388         this.config = config;
389     }
390
391     // See ScanManagerMXBean
392
public ScanDirConfigMXBean getConfigurationMBean() {
393         return config;
394     }
395
396     // Creates and registers a new directory scanner.
397
// Called by applyConfiguration.
398
// throws IllegalStateException if state is not STOPPED or COMPLETED
399
// (you cannot change the config while scanning is scheduled or running).
400
//
401
private DirectoryScannerMXBean addDirectoryScanner(
402                 DirectoryScannerConfig bean)
403         throws JMException JavaDoc {
404         try {
405             final DirectoryScannerMXBean scanner;
406             final ObjectName JavaDoc scanName;
407             synchronized (this) {
408                 if (state != STOPPED && state != COMPLETED)
409                    throw new IllegalStateException JavaDoc(state.toString());
410                 scanner = createDirectoryScanner(bean);
411                 scanName = makeDirectoryScannerName(bean.getName());
412             }
413             LOG.fine("server: "+mbeanServer);
414             LOG.fine("scanner: "+scanner);
415             LOG.fine("scanName: "+scanName);
416             final ObjectInstance JavaDoc moi =
417                 mbeanServer.registerMBean(scanner,scanName);
418             final ObjectName JavaDoc moiName = moi.getObjectName();
419             final DirectoryScannerMXBean proxy =
420                 JMX.newMXBeanProxy(mbeanServer,moiName,
421                 DirectoryScannerMXBean.class,true);
422             scanmap.put(moiName,proxy);
423             return proxy;
424         } catch (RuntimeException JavaDoc x) {
425             final String JavaDoc msg = "Operation failed: "+x;
426             if (LOG.isLoggable(Level.FINEST))
427                 LOG.log(Level.FINEST,msg,x);
428             else LOG.fine(msg);
429             throw x;
430         } catch (JMException JavaDoc x) {
431             final String JavaDoc msg = "Operation failed: "+x;
432             if (LOG.isLoggable(Level.FINEST))
433                 LOG.log(Level.FINEST,msg,x);
434             else LOG.fine(msg);
435             throw x;
436         }
437     }
438
439     // See ScanManagerMXBean
440
public ScanDirConfigMXBean createOtherConfigurationMBean(String JavaDoc name,
441             String JavaDoc filename)
442         throws JMException JavaDoc {
443         final ScanDirConfig profile = new ScanDirConfig(filename);
444         final ObjectName JavaDoc profName = makeScanDirConfigName(name);
445         final ObjectInstance JavaDoc moi = mbeanServer.registerMBean(profile,profName);
446         final ScanDirConfigMXBean proxy =
447                 JMX.newMXBeanProxy(mbeanServer,profName,
448                     ScanDirConfigMXBean.class,true);
449         configmap.put(moi.getObjectName(),proxy);
450         return proxy;
451     }
452
453     
454     // See ScanManagerMXBean
455
public Map JavaDoc<String JavaDoc,DirectoryScannerMXBean> getDirectoryScanners() {
456         final Map JavaDoc<String JavaDoc,DirectoryScannerMXBean> proxyMap = newHashMap();
457         for (Entry<ObjectName JavaDoc,DirectoryScannerMXBean> item : scanmap.entrySet()){
458             proxyMap.put(item.getKey().getKeyProperty("name"),item.getValue());
459         }
460         return proxyMap;
461     }
462
463     // ---------------------------------------------------------------
464
// State Management
465
// ---------------------------------------------------------------
466

467     /**
468      * For each operation, this map stores a list of states from
469      * which the corresponding operation can be legally called.
470      * For instance, it is legal to call "stop" regardless of the
471      * application state. However, "schedule" can be called only if
472      * the application state is STOPPED, etc...
473      **/

474     private final static Map JavaDoc<String JavaDoc,EnumSet JavaDoc<ScanState>> allowedStates;
475     static {
476         allowedStates = newHashMap();
477         // You can always call stop
478
allowedStates.put("stop",EnumSet.allOf(ScanState.class));
479
480         // You can only call closed when stopped
481
allowedStates.put("close",EnumSet.of(STOPPED,COMPLETED,CLOSED));
482
483         // You can call schedule only when the current task is
484
// completed or stopped.
485
allowedStates.put("schedule",EnumSet.of(STOPPED,COMPLETED));
486
487         // switch reserved for background task: goes from SCHEDULED to
488
// RUNNING when it enters the run() method.
489
allowedStates.put("scan-running",EnumSet.of(SCHEDULED));
490
491         // switch reserved for background task: goes from RUNNING to
492
// SCHEDULED when it has completed but needs to reschedule
493
// itself for specified interval.
494
allowedStates.put("scan-scheduled",EnumSet.of(RUNNING));
495
496         // switch reserved for background task:
497
// goes from RUNNING to COMPLETED upon successful completion
498
allowedStates.put("scan-done",EnumSet.of(RUNNING));
499     }
500
501     // Get this object's state. No need to synchronize because
502
// state is volatile.
503
// See ScanManagerMXBean
504
public ScanState getState() {
505         return state;
506     }
507
508     /**
509      * Enqueue a state changed notification for the given states.
510      **/

511     private void queueStateChangedNotification(
512                     long sequence,
513                     long time,
514                     ScanState old,
515                     ScanState current) {
516         final AttributeChangeNotification JavaDoc n =
517                 new AttributeChangeNotification JavaDoc(SCAN_MANAGER_NAME,sequence,time,
518                 "ScanManager State changed to "+current,"State",
519                 ScanState.class.getName(),old.toString(),current.toString());
520         // Queue the notification. We have created an unlimited queue, so
521
// this method should always succeed.
522
try {
523             if (!pendingNotifs.offer(n,2,TimeUnit.SECONDS)) {
524                 LOG.fine("Can't queue Notification: "+n);
525             }
526         } catch (InterruptedException JavaDoc x) {
527                 LOG.fine("Can't queue Notification: "+x);
528         }
529     }
530
531     /**
532      * Send all notifications present in the queue.
533      **/

534     private void sendQueuedNotifications() {
535         Notification JavaDoc n;
536         while ((n = pendingNotifs.poll()) != null) {
537             broadcaster.sendNotification(n);
538         }
539     }
540
541     /**
542      * Checks that the current state is allowed for the given operation,
543      * and if so, switch its value to the new desired state.
544      * This operation also enqueue the appropriate state changed
545      * notification.
546      **/

547     private ScanState switchState(ScanState desired,String JavaDoc forOperation) {
548         return switchState(desired,allowedStates.get(forOperation));
549     }
550
551     /**
552      * Checks that the current state is one of the allowed states,
553      * and if so, switch its value to the new desired state.
554      * This operation also enqueue the appropriate state changed
555      * notification.
556      **/

557     private ScanState switchState(ScanState desired,EnumSet JavaDoc<ScanState> allowed) {
558         final ScanState old;
559         final long timestamp;
560         final long sequence;
561         synchronized(this) {
562             old = state;
563             if (!allowed.contains(state))
564                throw new IllegalStateException JavaDoc(state.toString());
565             state = desired;
566             timestamp = System.currentTimeMillis();
567             sequence = getNextSeqNumber();
568         }
569         LOG.fine("switched state: "+old+" -> "+desired);
570         if (old != desired)
571             queueStateChangedNotification(sequence,timestamp,old,desired);
572         return old;
573     }
574
575
576     // ---------------------------------------------------------------
577
// schedule() creates a new SessionTask that will be executed later
578
// (possibly right away if delay=0) by a Timer thread.
579
// ---------------------------------------------------------------
580

581     // The timer used by this object. Lazzy evaluation. Cleaned in
582
// postDeregister()
583
//
584
private Timer JavaDoc timer = null;
585
586     // See ScanManagerMXBean
587
public void schedule(long delay, long interval) {
588         if (!sequencer.tryAcquire()) {
589             throw new IllegalStateException JavaDoc("Can't acquire lock");
590         }
591         try {
592             LOG.fine("scheduling new task: state="+state);
593             final ScanState old = switchState(SCHEDULED,"schedule");
594             final boolean scheduled =
595                 scheduleSession(new SessionTask(interval),delay);
596             if (scheduled)
597                 LOG.fine("new task scheduled: state="+state);
598         } finally {
599             sequencer.release();
600         }
601         sendQueuedNotifications();
602     }
603
604     // Schedule a SessionTask. The session task may reschedule
605
// a new identical task when it eventually ends.
606
// We use this logic so that the 'interval' time is measured
607
// starting at the end of the task that finishes, rather than
608
// at its beginning. Therefore if a repeated task takes x ms,
609
// it will be repeated every x+interval ms.
610
//
611
private synchronized boolean scheduleSession(SessionTask task, long delay) {
612         if (state == STOPPED) return false;
613         if (timer == null) timer = new Timer JavaDoc("ScanManager");
614         tasklist.add(task);
615         timer.schedule(task,delay);
616         return true;
617     }
618
619     // ---------------------------------------------------------------
620
// start() is equivalent to schedule(0,0)
621
// ---------------------------------------------------------------
622

623     // See ScanManagerMXBean
624
public void start() throws IOException JavaDoc, InstanceNotFoundException JavaDoc {
625         schedule(0,0);
626     }
627
628     // ---------------------------------------------------------------
629
// Methods used to implement stop() - stop() is asynchronous,
630
// and needs to notify any running background task that it needs
631
// to stop. It also needs to prevent scheduled task from being
632
// run.
633
// ---------------------------------------------------------------
634

635     // See ScanManagerMXBean
636
public void stop() {
637         if (!sequencer.tryAcquire())
638             throw new IllegalStateException JavaDoc("Can't acquire lock");
639         int errcount = 0;
640         final StringBuilder JavaDoc b = new StringBuilder JavaDoc();
641
642         try {
643             switchState(STOPPED,"stop");
644
645             errcount += cancelSessionTasks(b);
646             errcount += stopDirectoryScanners(b);
647         } finally {
648             sequencer.release();
649         }
650
651         sendQueuedNotifications();
652         if (errcount > 0) {
653             b.insert(0,"stop partially failed with "+errcount+" error(s):");
654             throw new RuntimeException JavaDoc(b.toString());
655         }
656     }
657
658     // See ScanManagerMXBean
659
public void close() {
660         switchState(CLOSED,"close");
661         sendQueuedNotifications();
662     }
663
664     // Appends exception to a StringBuilder message.
665
//
666
private void append(StringBuilder JavaDoc b,String JavaDoc prefix,Throwable JavaDoc t) {
667         final String JavaDoc first = (prefix==null)?"\n":"\n"+prefix;
668         b.append(first).append(String.valueOf(t));
669         Throwable JavaDoc cause = t;
670         while ((cause = cause.getCause())!=null) {
671             b.append(first).append("Caused by:").append(first);
672             b.append('\t').append(String.valueOf(cause));
673         }
674     }
675
676     // Cancels all scheduled session tasks
677
//
678
private int cancelSessionTasks(StringBuilder JavaDoc b) {
679         int errcount = 0;
680         // Stops scheduled tasks if any...
681
//
682
for (SessionTask task : tasklist) {
683             try {
684                 task.cancel();
685                 tasklist.remove(task);
686             } catch (Exception JavaDoc ex) {
687                 errcount++;
688                 append(b,"\t",ex);
689             }
690         }
691         return errcount;
692     }
693
694     // Stops all DirectoryScanners configured for this object.
695
//
696
private int stopDirectoryScanners(StringBuilder JavaDoc b) {
697         int errcount = 0;
698         // Stops directory scanners if any...
699
//
700
for (DirectoryScannerMXBean s : scanmap.values()) {
701             try {
702                 s.stop();
703             } catch (Exception JavaDoc ex) {
704                 errcount++;
705                 append(b,"\t",ex);
706             }
707         }
708         return errcount;
709     }
710
711
712     // ---------------------------------------------------------------
713
// We start scanning in background in a Timer thread.
714
// The methods below implement that logic.
715
// ---------------------------------------------------------------
716

717     private void scanAllDirectories()
718         throws IOException JavaDoc, InstanceNotFoundException JavaDoc {
719
720         int errcount = 0;
721         final StringBuilder JavaDoc b = new StringBuilder JavaDoc();
722         for (ObjectName JavaDoc key : scanmap.keySet()) {
723             final DirectoryScannerMXBean s = scanmap.get(key);
724             try {
725                 if (state == STOPPED) return;
726                 s.scan();
727             } catch (Exception JavaDoc ex) {
728                 LOG.log(Level.FINE,key + " failed to scan: "+ex,ex);
729                 errcount++;
730                 append(b,"\t",ex);
731             }
732         }
733         if (errcount > 0) {
734             b.insert(0,"scan partially performed with "+errcount+" error(s):");
735             throw new RuntimeException JavaDoc(b.toString());
736         }
737     }
738
739     // List of scheduled session task. Needed by stop() to cancel
740
// scheduled sessions. There's usually at most 1 session in
741
// this list (unless there's a bug somewhere ;-))
742
//
743
private final ConcurrentLinkedQueue JavaDoc<SessionTask> tasklist =
744             new ConcurrentLinkedQueue JavaDoc<SessionTask>();
745
746     // Used to give a unique id to session task - useful for
747
// debugging.
748
//
749
private volatile static long taskcount = 0;
750
751     /**
752      * A session task will be scheduled to run in background in a
753      * timer thread. There can be at most one session task running
754      * at a given time (this is ensured by using a timer - which is
755      * a single threaded object).
756      *
757      * If the session needs to be repeated, it will reschedule an
758      * identical session when it finishes to run. This ensure that
759      * two session runs are separated by the given interval time.
760      *
761      **/

762     private class SessionTask extends TimerTask JavaDoc {
763
764         /**
765          * Delay after which the next iteration of this task will
766          * start. This delay is measured starting at the end of
767          * the previous iteration.
768          **/

769         final long delayBeforeNext;
770
771         /**
772          * A unique id for this task.
773          **/

774         final long taskid;
775
776         /**
777          * Whether it's been cancelled by stop()
778          **/

779         volatile boolean cancelled=false;
780
781         /**
782          * create a new SessionTask.
783          **/

784         SessionTask(long scheduleNext) {
785             delayBeforeNext = scheduleNext;
786             taskid = taskcount++;
787         }
788
789         /**
790          * When run() begins, the state is switched to RUNNING.
791          * When run() ends then:
792          * If the task is repeated, the state will be switched
793          * to SCHEDULED (because a new task was scheduled).
794          * Otherwise the state will be switched to either
795          * STOPPED (if it was stopped before it could complete)
796          * or COMPLETED (if it completed gracefully)
797          * This method is used to switch to the desired state and
798          * send the appropriate notifications.
799          * When entering the method, we check whether the state is
800          * STOPPED. If so, we return false - and the SessionTask will
801          * stop. Otherwise, we switch the state to the desired value.
802          **/

803         private boolean notifyStateChange(ScanState newState,String JavaDoc condition) {
804             synchronized (ScanManager.this) {
805                 if (state == STOPPED || state == CLOSED) return false;
806                 switchState(newState,condition);
807             }
808             sendQueuedNotifications();
809             return true;
810         }
811
812         // Cancels this task.
813
public boolean cancel() {
814             cancelled=true;
815             return super.cancel();
816         }
817
818         /**
819          * Invoke all directories scanners in sequence. At each
820          * step, checks to see whether the task should stop.
821          **/

822         private boolean execute() {
823             final String JavaDoc tag = "Scheduled session["+taskid+"]";
824             try {
825                 if (cancelled) {
826                     LOG.finer(tag+" cancelled: done");
827                     return false;
828                 }
829                 if (!notifyStateChange(RUNNING,"scan-running")) {
830                     LOG.finer(tag+" stopped: done");
831                     return false;
832                 }
833                 scanAllDirectories();
834             } catch (Exception JavaDoc x) {
835                 if (LOG.isLoggable(Level.FINEST)) {
836                     LOG.log(Level.FINEST,
837                             tag+" failed to scan: "+x,x);
838                 } else if (LOG.isLoggable(Level.FINE)) {
839                     LOG.fine(tag+" failed to scan: "+x);
840                 }
841             }
842             return true;
843         }
844
845         /**
846          * Schedule an identical task for next iteration.
847          **/

848         private boolean scheduleNext() {
849             final String JavaDoc tag = "Scheduled session["+taskid+"]";
850
851             // We need now to reschedule a new task for after 'delayBeforeNext' ms.
852
try {
853                 LOG.finer(tag+": scheduling next session for "+ delayBeforeNext + "ms");
854                 if (cancelled || !notifyStateChange(SCHEDULED,"scan-scheduled")) {
855                     LOG.finer(tag+" stopped: do not reschedule");
856                     return false;
857                 }
858                 final SessionTask nextTask = new SessionTask(delayBeforeNext);
859                 if (!scheduleSession(nextTask,delayBeforeNext)) return false;
860                 LOG.finer(tag+": next session successfully scheduled");
861             } catch (Exception JavaDoc x) {
862                 if (LOG.isLoggable(Level.FINEST)) {
863                     LOG.log(Level.FINEST,tag+
864                             " failed to schedule next session: "+x,x);
865                 } else if (LOG.isLoggable(Level.FINE)) {
866                     LOG.fine(tag+" failed to schedule next session: "+x);
867                 }
868             }
869             return true;
870         }
871
872
873         /**
874          * The run method:
875          * executes scanning logic, the schedule next iteration if needed.
876          **/

877         public void run() {
878             final String JavaDoc tag = "Scheduled session["+taskid+"]";
879             LOG.entering(SessionTask.class.getName(),"run");
880             LOG.finer(tag+" starting...");
881             try {
882                 if (execute()==false) return;
883
884                 LOG.finer(tag+" terminating - state is "+state+
885                     ((delayBeforeNext >0)?(" next session is due in "+delayBeforeNext+" ms."):
886                         " no additional session scheduled"));
887
888                 // if delayBeforeNext <= 0 we are done, either because the session was
889
// stopped or because it successfully completed.
890
if (delayBeforeNext <= 0) {
891                     if (!notifyStateChange(COMPLETED,"scan-done"))
892                         LOG.finer(tag+" stopped: done");
893                     else
894                         LOG.finer(tag+" completed: done");
895                     return;
896                 }
897
898                 // we need to reschedule a new session for 'delayBeforeNext' ms.
899
scheduleNext();
900
901             } finally {
902                 tasklist.remove(this);
903                 LOG.finer(tag+" finished...");
904                 LOG.exiting(SessionTask.class.getName(),"run");
905             }
906         }
907     }
908
909     // ---------------------------------------------------------------
910
// ---------------------------------------------------------------
911

912     // ---------------------------------------------------------------
913
// MBean Notification support
914
// The methods below are imported from {@link NotificationEmitter}
915
// ---------------------------------------------------------------
916

917     /**
918      * Delegates the implementation of this method to the wrapped
919      * {@code NotificationBroadcasterSupport} object.
920      **/

921     public void addNotificationListener(NotificationListener JavaDoc listener, NotificationFilter JavaDoc filter, Object JavaDoc handback) throws IllegalArgumentException JavaDoc {
922         broadcaster.addNotificationListener(listener, filter, handback);
923     }
924
925
926     /**
927      * We emit an {@code AttributeChangeNotification} when the {@code State}
928      * attribute changes.
929      **/

930     public MBeanNotificationInfo JavaDoc[] getNotificationInfo() {
931         return new MBeanNotificationInfo JavaDoc[] {
932             new MBeanNotificationInfo JavaDoc(new String JavaDoc[] {
933                 AttributeChangeNotification.ATTRIBUTE_CHANGE},
934                 AttributeChangeNotification JavaDoc.class.getName(),
935                 "Emitted when the State attribute changes")
936             };
937     }
938
939     /**
940      * Delegates the implementation of this method to the wrapped
941      * {@code NotificationBroadcasterSupport} object.
942      **/

943     public void removeNotificationListener(NotificationListener JavaDoc listener) throws ListenerNotFoundException JavaDoc {
944         broadcaster.removeNotificationListener(listener);
945     }
946
947     /**
948      * Delegates the implementation of this method to the wrapped
949      * {@code NotificationBroadcasterSupport} object.
950      **/

951     public void removeNotificationListener(NotificationListener JavaDoc listener, NotificationFilter JavaDoc filter, Object JavaDoc handback) throws ListenerNotFoundException JavaDoc {
952         broadcaster.removeNotificationListener(listener, filter, handback);
953     }
954
955     /**
956      * Returns and increment the sequence number used for
957      * notifications. We use the same sequence number throughout the
958      * application - this is why this method is only package protected.
959      * @return A unique sequence number for the next notification.
960      */

961     static synchronized long getNextSeqNumber() {
962         return seqNumber++;
963     }
964
965     // ---------------------------------------------------------------
966
// End of MBean Notification support
967
// ---------------------------------------------------------------
968

969     // ---------------------------------------------------------------
970
// MBeanRegistration support
971
// The methods below are imported from {@link MBeanRegistration}
972
// ---------------------------------------------------------------
973

974     /**
975      * Allows the MBean to perform any operations it needs before being
976      * registered in the MBean server. If the name of the MBean is not
977      * specified, the MBean can provide a name for its registration. If
978      * any exception is raised, the MBean will not be registered in the
979      * MBean server.
980      * <p>In this implementation, we check that the provided name is
981      * either {@code null} or equals to {@link #SCAN_MANAGER_NAME}. If it
982      * isn't then we throw an IllegalArgumentException, otherwise we return
983      * {@link #SCAN_MANAGER_NAME}.</p>
984      * <p>This ensures that there will be a single instance of ScanManager
985      * registered in a given MBeanServer, and that it will always be
986      * registered with the singleton's {@link #SCAN_MANAGER_NAME}.</p>
987      * <p>We do not need to check whether an MBean by that name is
988      * already registered because the MBeanServer will perform
989      * this check just after having called preRegister().</p>
990      * @param server The MBean server in which the MBean will be registered.
991      * @param name The object name of the MBean. This name is null if the
992      * name parameter to one of the createMBean or registerMBean methods in
993      * the MBeanServer interface is null. In that case, this method must
994      * return a non-null ObjectName for the new MBean.
995      * @return The name under which the MBean is to be registered. This value
996      * must not be null. If the name parameter is not null, it will usually
997      * but not necessarily be the returned value.
998      * @throws Exception This exception will be caught by the MBean server and
999      * re-thrown as an MBeanRegistrationException.
1000     */

1001    public ObjectName JavaDoc preRegister(MBeanServer JavaDoc server, ObjectName JavaDoc name) throws Exception JavaDoc {
1002        if (name != null) {
1003            if (!SCAN_MANAGER_NAME.equals(name))
1004                throw new IllegalArgumentException JavaDoc(String.valueOf(name));
1005        }
1006        mbeanServer = server;
1007        return SCAN_MANAGER_NAME;
1008    }
1009
1010    // Returns the default configuration filename
1011
static String JavaDoc getDefaultConfigurationFileName() {
1012        // This is a file calles 'jmx-scandir.xml' located
1013
// in the user directory.
1014
final String JavaDoc user = System.getProperty("user.home");
1015        final String JavaDoc defconf = user+File.separator+"jmx-scandir.xml";
1016        return defconf;
1017    }
1018
1019    /**
1020     * Allows the MBean to perform any operations needed after having
1021     * been registered in the MBean server or after the registration has
1022     * failed.
1023     * <p>
1024     * If registration was not successful, the method returns immediately.
1025     * <p>
1026     * If registration is successful, register the {@link ResultLogManager}
1027     * and default {@link ScanDirConfigMXBean}. If registering these
1028     * MBean fails, the {@code ScanManager} state will be switched to
1029     * {@link #close CLOSED}, and postRegister ends there.
1030     * </p>
1031     * <p>Otherwise the {@code ScanManager} will ask the
1032     * {@link ScanDirConfigMXBean} to load its configuration.
1033     * If it succeeds, the configuration will be {@link
1034     * #applyConfiguration applied}. Otherwise, the method simply returns,
1035     * assuming that the user will later create/update a configuration and
1036     * apply it.
1037     * @param registrationDone Indicates whether or not the MBean has been
1038     * successfully registered in the MBean server. The value false means
1039     * that the registration has failed.
1040     */

1041    public void postRegister(Boolean JavaDoc registrationDone) {
1042        if (!registrationDone) return;
1043        Exception JavaDoc test=null;
1044        try {
1045            mbeanServer.registerMBean(log,
1046                    ResultLogManager.RESULT_LOG_MANAGER_NAME);
1047            final String JavaDoc defconf = getDefaultConfigurationFileName();
1048            final String JavaDoc conf = System.getProperty("scandir.config.file",defconf);
1049            final String JavaDoc confname = ScanDirConfig.guessConfigName(conf,defconf);
1050            final ObjectName JavaDoc defaultProfileName =
1051                    makeMBeanName(ScanDirConfigMXBean.class,confname);
1052            if (!mbeanServer.isRegistered(defaultProfileName))
1053                mbeanServer.registerMBean(new ScanDirConfig(conf),
1054                        defaultProfileName);
1055            config = JMX.newMXBeanProxy(mbeanServer,defaultProfileName,
1056                    ScanDirConfigMXBean.class,true);
1057            configmap.put(defaultProfileName,config);
1058        } catch (Exception JavaDoc x) {
1059            LOG.config("Failed to populate MBeanServer: "+x);
1060            close();
1061            return;
1062        }
1063        try {
1064            config.load();
1065        } catch (Exception JavaDoc x) {
1066            LOG.finest("No config to load: "+x);
1067            test = x;
1068        }
1069        if (test == null) {
1070            try {
1071                applyConfiguration(config.getConfiguration());
1072            } catch (Exception JavaDoc x) {
1073                if (LOG.isLoggable(Level.FINEST))
1074                    LOG.log(Level.FINEST,"Failed to apply config: "+x,x);
1075                LOG.config("Failed to apply config: "+x);
1076            }
1077        }
1078    }
1079
1080    // Unregisters all created DirectoryScanners
1081
private void unregisterScanners() throws JMException JavaDoc {
1082        unregisterMBeans(scanmap);
1083    }
1084
1085    // Unregisters all created ScanDirConfigs
1086
private void unregisterConfigs() throws JMException JavaDoc {
1087        unregisterMBeans(configmap);
1088    }
1089
1090    // Unregisters all MBeans named by the given map
1091
private void unregisterMBeans(Map JavaDoc<ObjectName JavaDoc,?> map) throws JMException JavaDoc {
1092        for (ObjectName JavaDoc key : map.keySet()) {
1093            if (mbeanServer.isRegistered(key))
1094                mbeanServer.unregisterMBean(key);
1095            map.remove(key);
1096        }
1097    }
1098
1099    // Unregisters the ResultLogManager.
1100
private void unregisterResultLogManager() throws JMException JavaDoc {
1101        final ObjectName JavaDoc name = ResultLogManager.RESULT_LOG_MANAGER_NAME;
1102        if (mbeanServer.isRegistered(name)) {
1103            mbeanServer.unregisterMBean(name);
1104        }
1105    }
1106
1107    /**
1108     * Allows the MBean to perform any operations it needs before being
1109     * unregistered by the MBean server.
1110     * This implementation also unregisters all the MXBeans
1111     * that were created by this object.
1112     * @throws IllegalStateException if the lock can't be acquire, or if
1113     * the MBean's state doesn't allow the MBean to be unregistered
1114     * (e.g. because it's scheduled or running).
1115     * @throws Exception This exception will be caught by the MBean server and
1116     * re-thrown as an MBeanRegistrationException.
1117     */

1118    public void preDeregister() throws Exception JavaDoc {
1119        try {
1120            close();
1121            if (!sequencer.tryAcquire())
1122                throw new IllegalStateException JavaDoc("can't acquire lock");
1123            try {
1124                unregisterScanners();
1125                unregisterConfigs();
1126                unregisterResultLogManager();
1127            } finally {
1128                sequencer.release();
1129            }
1130        } catch (Exception JavaDoc x) {
1131            LOG.log(Level.FINEST,"Failed to unregister: "+x,x);
1132            throw x;
1133        }
1134    }
1135
1136    /**
1137     * Allows the MBean to perform any operations needed after having been
1138     * unregistered in the MBean server.
1139     * Cancels the internal timer - if any.
1140     */

1141    public synchronized void postDeregister() {
1142        if (timer != null) {
1143            try {
1144                timer.cancel();
1145            } catch (Exception JavaDoc x) {
1146                if (LOG.isLoggable(Level.FINEST))
1147                    LOG.log(Level.FINEST,"Failed to cancel timer",x);
1148                else if (LOG.isLoggable(Level.FINE))
1149                    LOG.fine("Failed to cancel timer: "+x);
1150            } finally {
1151                timer = null;
1152            }
1153        }
1154   }
1155
1156    // ---------------------------------------------------------------
1157
// End of MBeanRegistration support
1158
// ---------------------------------------------------------------
1159

1160}
1161
1162
Popular Tags