KickJava   Java API By Example, From Geeks To Geeks.

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


1 /*
2  * DirectoryScanner.java
3  *
4  * Created on July 10, 2006, 4:20 PM
5  *
6  * @(#)DirectoryScanner.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.ScanManager.getNextSeqNumber;
44 import com.sun.jmx.examples.scandir.ScanManagerMXBean.ScanState;
45 import static com.sun.jmx.examples.scandir.ScanManagerMXBean.ScanState.*;
46 import static com.sun.jmx.examples.scandir.config.DirectoryScannerConfig.Action.*;
47 import com.sun.jmx.examples.scandir.config.XmlConfigUtils;
48 import com.sun.jmx.examples.scandir.config.DirectoryScannerConfig;
49 import com.sun.jmx.examples.scandir.config.DirectoryScannerConfig.Action;
50 import com.sun.jmx.examples.scandir.config.ResultRecord;
51 import java.io.File JavaDoc;
52 import java.io.FileFilter JavaDoc;
53 import java.io.IOException JavaDoc;
54 import java.util.Arrays JavaDoc;
55 import java.util.Collections JavaDoc;
56 import java.util.EnumSet JavaDoc;
57 import java.util.HashSet JavaDoc;
58 import java.util.LinkedList JavaDoc;
59 import java.util.Set JavaDoc;
60 import java.util.logging.Level JavaDoc;
61 import java.util.logging.Logger JavaDoc;
62 import javax.management.AttributeChangeNotification JavaDoc;
63 import javax.management.InstanceNotFoundException JavaDoc;
64 import javax.management.ListenerNotFoundException JavaDoc;
65 import javax.management.MBeanNotificationInfo JavaDoc;
66 import javax.management.Notification JavaDoc;
67 import javax.management.NotificationBroadcasterSupport JavaDoc;
68 import javax.management.NotificationEmitter JavaDoc;
69 import javax.management.NotificationFilter JavaDoc;
70 import javax.management.NotificationListener JavaDoc;
71
72 /**
73  * A <code>DirectoryScanner</code> is an MBean that
74  * scans a file system starting at a given root directory,
75  * and then looks for files that match a given criteria.
76  * <p>
77  * When such a file is found, the <code>DirectoryScanner</code> takes
78  * the action for which it was configured: emit a notification,
79  * <i>and or</i> log a {@link
80  * com.sun.jmx.examples.scandir.config.ResultRecord} for this file,
81  * <i>and or</i> delete that file.
82  * </p>
83  * <p>
84  * The code that would actually delete the file is commented out - so that
85  * nothing valuable is lost if this example is run by mistake on the wrong
86  * set of directories.<br>
87  * Logged results are logged by sending them to the {@link ResultLogManager}.
88  * </p>
89  * <p>
90  * <code>DirectoryScannerMXBeans</code> are created, initialized, and
91  * registered by the {@link ScanManagerMXBean}.
92  * The {@link ScanManagerMXBean} will also schedule and run them in
93  * background by calling their {@link #scan} method.
94  * </p>
95  * <p>Client code is not expected to create or register directly any such
96  * MBean. Instead, clients are expected to modify the configuration, using
97  * the {@link ScanDirConfigMXBean}, and then apply it, using the {@link
98  * ScanManagerMXBean}. Instances of <code>DirectoryScannerMXBeans</code>
99  * will then be created and registered (or unregistered and garbage collected)
100  * as a side effect of applying that configuration.
101  * </p>
102  *
103  * @author Sun Microsystems, 2006 - All rights reserved.
104  */

105 public class DirectoryScanner implements
106         DirectoryScannerMXBean, NotificationEmitter JavaDoc {
107
108     /**
109      * The type for <i>com.sun.jmx.examples.scandir.filematch</i> notifications.
110      * Notifications of this type will be emitted whenever a file that
111      * matches this {@code DirectoryScanner} criteria is found, but only if
112      * this {@code DirectoryScanner} was configured to {@link
113      * Action#NOTIFY notify} for matching files.
114      **/

115     public static final String JavaDoc FILE_MATCHES_NOTIFICATION =
116             "com.sun.jmx.examples.scandir.filematch";
117
118     /**
119      * A logger for this class.
120      **/

121     private static final Logger JavaDoc LOG =
122             Logger.getLogger(DirectoryScanner.class.getName());
123     
124     // Attribute : State
125
//
126
private volatile ScanState state = STOPPED;
127
128     // The DirectoryScanner delegates the implementation of
129
// the NotificationEmitter interface to a wrapped instance
130
// of NotificationBroadcasterSupport.
131
//
132
private final NotificationBroadcasterSupport JavaDoc broadcaster;
133     
134     // The root directory at which this DirectoryScanner will start
135
// scanning. Constructed from config.getRootDirectory().
136
//
137
private final File JavaDoc rootFile;
138
139     // This DirectoryScanner config - this is a constant which is
140
// provided at construction time by the {@link ScanManager}.
141
//
142
private final DirectoryScannerConfig config;
143     
144     // The set of actions for which this DirectoryScanner is configured.
145
// Constructed from config.getActions()
146
//
147
final Set JavaDoc<Action> actions;
148     
149     // The ResultLogManager that this DirectoryScanner will use to log
150
// info. This is a hard reference to another MBean, provided
151
// at construction time by the ScanManager.
152
// The ScanManager makes sure that the life cycle of these two MBeans
153
// is consistent.
154
//
155
final ResultLogManager logManager;
156     
157     /**
158      * Constructs a new {@code DirectoryScanner}.
159      * <p>This constructor is
160      * package protected, and this MBean cannot be created by a remote
161      * client, because it needs a reference to the {@link ResultLogManager},
162      * which cannot be provided from remote.
163      * </p>
164      * <p>This is a conscious design choice: {@code DirectoryScanner} MBeans
165      * are expected to be completely managed (created, registered, unregistered)
166      * by the {@link ScanManager} which does provide this reference.
167      * </p>
168      *
169      * @param config This {@code DirectoryScanner} configuration.
170      * @param logManager The info log manager with which to log the info
171      * records.
172      * @throws IllegalArgumentException if one of the parameter is null, or if
173      * the provided {@code config} doesn't have its {@code name} set,
174      * or if the {@link DirectoryScannerConfig#getRootDirectory
175      * root directory} provided in the {@code config} is not acceptable
176      * (not provided or not found or not readable, etc...).
177      **/

178     public DirectoryScanner(DirectoryScannerConfig config,
179                             ResultLogManager logManager)
180         throws IllegalArgumentException JavaDoc {
181         if (logManager == null)
182             throw new IllegalArgumentException JavaDoc("log=null");
183         if (config == null)
184             throw new IllegalArgumentException JavaDoc("config=null");
185         if (config.getName() == null)
186             throw new IllegalArgumentException JavaDoc("config.name=null");
187         
188          broadcaster = new NotificationBroadcasterSupport JavaDoc();
189          
190          // Clone the config: ensure data encapsulation.
191
//
192
this.config = XmlConfigUtils.xmlClone(config);
193          
194          // Checks that the provided root directory is valid.
195
// Throws IllegalArgumentException if it isn't.
196
//
197
rootFile = validateRoot(config.getRootDirectory());
198          
199          // Initialize the Set<Action> for which this DirectoryScanner
200
// is configured.
201
//
202
if (config.getActions() == null)
203              actions = Collections.emptySet();
204          else
205              actions = EnumSet.copyOf(Arrays.asList(config.getActions()));
206          this.logManager = logManager;
207     }
208
209     // see DirectoryScannerMXBean
210
public void stop() {
211         // switch state to stop and send AttributeValueChangeNotification
212
setStateAndNotify(STOPPED);
213     }
214
215     // see DirectoryScannerMXBean
216
public String JavaDoc getRootDirectory() {
217         return rootFile.getAbsolutePath();
218     }
219     
220     
221     // see DirectoryScannerMXBean
222
public ScanState getState() {
223         return state;
224     }
225
226     // see DirectoryScannerMXBean
227
public DirectoryScannerConfig getConfiguration() {
228         return config;
229     }
230
231     // see DirectoryScannerMXBean
232
public String JavaDoc getCurrentScanInfo() {
233         final ScanTask currentOrLastTask = currentTask;
234         if (currentOrLastTask == null) return "Never Run";
235         return currentOrLastTask.getScanInfo();
236     }
237
238     // This variable points to the current (or latest) scan.
239
//
240
private volatile ScanTask currentTask = null;
241
242     // see DirectoryScannerMXBean
243
public void scan() {
244         final ScanTask task;
245         
246         synchronized (this) {
247             final LinkedList JavaDoc<File JavaDoc> list;
248             switch (state) {
249                 case RUNNING:
250                 case SCHEDULED:
251                     throw new IllegalStateException JavaDoc(state.toString());
252                 case STOPPED:
253                 case COMPLETED:
254                     // only accept to scan if state is STOPPED or COMPLETED.
255
list = new LinkedList JavaDoc<File JavaDoc>();
256                     list.add(rootFile);
257                     break;
258                 default:
259                     throw new IllegalStateException JavaDoc(String.valueOf(state));
260             }
261             
262             // Create a new ScanTask object for our root directory file.
263
//
264
currentTask = task = new ScanTask(list,this);
265             
266             // transient state... will be switched to RUNNING when
267
// task.execute() is called. This code could in fact be modified
268
// to use java.util.concurent.Future and, to push the task to
269
// an executor. We would then need to wait for the task to
270
// complete before returning. However, this wouldn't buy us
271
// anything - since this method should wait for the task to
272
// finish anyway: so why would we do it?
273
// As it stands, we simply call task.execute() in the current
274
// thread - brave and fearless readers may want to attempt the
275
// modification ;-)
276
//
277
setStateAndNotify(SCHEDULED);
278         }
279         task.execute();
280     }
281     
282     // This method is invoked to carry out the configured actions on a
283
// matching file.
284
// Do not call this method from within synchronized() { } as this
285
// method may send notifications!
286
//
287
void actOn(File JavaDoc file) {
288         
289         // Which action were actually taken
290
//
291
final Set JavaDoc<Action> taken = new HashSet JavaDoc<Action>();
292         boolean logresult = false;
293         
294         // Check out which actions are configured and carry them out.
295
//
296
for (Action action : actions) {
297             switch (action) {
298                 case DELETE:
299                     if (deleteFile(file)) {
300                         // Delete succeeded: add DELETE to the set of
301
// actions carried out.
302
taken.add(DELETE);
303                     }
304                     break;
305                 case NOTIFY:
306                     if (notifyMatch(file)) {
307                         // Notify succeeded: add NOTIFY to the set of
308
// actions carried out.
309
taken.add(NOTIFY);
310                     }
311                     break;
312                 case LOGRESULT:
313                     // LOGRESULT was configured - log actions carried out.
314
// => we must execute this action as the last action.
315
// simply set logresult=true for now. We will do
316
// the logging later
317
logresult = true;
318                     break;
319                 default:
320                     LOG.fine("Failed to execute action: " +action +
321                             " - action not supported");
322                     break;
323             }
324         }
325         
326         // Now is time for logging:
327
if (logresult) {
328             taken.add(LOGRESULT);
329             if (!logResult(file,taken.toArray(new Action[taken.size()])))
330                 taken.remove(LOGRESULT); // just for the last trace below...
331
}
332         
333         LOG.finest("File processed: "+taken+" - "+file.getAbsolutePath());
334     }
335     
336     // Deletes a matching file.
337
private boolean deleteFile(File JavaDoc file) {
338         try {
339             // file.delete() is commented so that we don't do anything
340
// bad if the example is mistakenly run on the wrong set of
341
// directories.
342
//
343
/* file.delete(); */
344             System.out.println("DELETE not implemented for safety reasons.");
345             return true;
346         } catch (Exception JavaDoc x) {
347             LOG.fine("Failed to delete: "+file.getAbsolutePath());
348         }
349         return false;
350     }
351     
352     // Notifies of a matching file.
353
private boolean notifyMatch(File JavaDoc file) {
354         try {
355             final Notification JavaDoc n =
356                     new Notification JavaDoc(FILE_MATCHES_NOTIFICATION,this,
357                     getNextSeqNumber(),
358                     file.getAbsolutePath());
359             
360             // This method *is not* called from any synchronized block, so
361
// we can happily call broadcaster.sendNotification() here.
362
// Note that verifying whether a method is called from within
363
// a synchronized block demends a thoroughful code reading,
364
// examining each of the 'parent' methods in turn.
365
//
366
broadcaster.sendNotification(n);
367             return true;
368         } catch (Exception JavaDoc x) {
369             LOG.fine("Failed to notify: "+file.getAbsolutePath());
370         }
371         return false;
372     }
373     
374     // Logs a result with the ResultLogManager
375
private boolean logResult(File JavaDoc file,Action[] actions) {
376         try {
377             logManager.log(new ResultRecord(config, actions,file));
378             return true;
379         } catch (Exception JavaDoc x) {
380             LOG.fine("Failed to log: "+file.getAbsolutePath());
381         }
382         return false;
383     }
384     
385
386     // Contextual object used to store info about current
387
// (or last) scan.
388
//
389
private static class ScanTask {
390         
391         // List of Files that remain to scan.
392
// When files are discovered they are added to the list.
393
// When they are being handled, they are removed from the list.
394
// When the list is empty, the scanning is finished.
395
//
396
private final LinkedList JavaDoc<File JavaDoc> list;
397         private final DirectoryScanner scan;
398         
399         // Some statistics...
400
//
401
private volatile long scanned=0;
402         private volatile long matching=0;
403         
404         private volatile String JavaDoc info="Not started";
405         
406         ScanTask(LinkedList JavaDoc<File JavaDoc> list, DirectoryScanner scan) {
407             this.list = list; this.scan = scan;
408         }
409         
410         public void execute() {
411             scan(list);
412         }
413         
414         private void scan(LinkedList JavaDoc<File JavaDoc> list) {
415              scan.scan(this,list);
416         }
417         
418         public String JavaDoc getScanInfo() {
419             return info+" - ["+scanned+" scanned, "+matching+" matching]";
420         }
421     }
422     
423     // The actual scan logic. Switches state to RUNNING,
424
// and scan the list of given dirs.
425
// The list is a live object which is updated by this method.
426
// This would allow us to implement methods like "pause" and "resume",
427
// since all the info needed to resume would be in the list.
428
//
429
private void scan(ScanTask task, LinkedList JavaDoc<File JavaDoc> list) {
430         setStateAndNotify(RUNNING);
431         task.info = "In Progress";
432         try {
433             
434             // The FileFilter will tell us which files match and which don't.
435
//
436
final FileFilter JavaDoc filter = config.buildFileFilter();
437             
438             // We have two condition to end the loop: either the list is
439
// empty, meaning there's nothing more to scan, or the state of
440
// the DirectoryScanner was asynchronously switched to STOPPED by
441
// another thread, e.g. because someone called "stop" on the
442
// ScanManagerMXBean
443
//
444
while (!list.isEmpty() && state == RUNNING) {
445                 
446                 // Get and remove the first element in the list.
447
//
448
final File JavaDoc current = list.poll();
449                 
450                 // Increment number of file scanned.
451
task.scanned++;
452                 
453                 // If 'current' is a file, it's already been matched by our
454
// file filter (see below): act on it.
455
// Note that for the first iteration of this loop, there will
456
// be one single file in the list: the root directory for this
457
// scanner.
458
//
459
if (current.isFile()) {
460                     task.matching++;
461                     actOn(current);
462                 }
463                 
464                 // If 'current' is a directory, then
465
// find files and directories that match the file filter
466
// in this directory
467
//
468
if (current.isDirectory()) {
469                     
470                     // Gets matching files and directories
471
final File JavaDoc[] content = current.listFiles(filter);
472                     if (content == null) continue;
473                     
474                     // Adds all matching file to the list.
475
list.addAll(0,Arrays.asList(content));
476                 }
477             }
478             
479             // The loop terminated. If the list is empty, then we have
480
// completed our task. If not, then somebody must have called
481
// stop() on this directory scanner.
482
//
483
if (list.isEmpty()) {
484                 task.info = "Successfully Completed";
485                 setStateAndNotify(COMPLETED);
486             }
487         } catch (Exception JavaDoc x) {
488             // We got an exception: stop the scan
489
//
490
task.info = "Failed: "+x;
491             if (LOG.isLoggable(Level.FINEST))
492                 LOG.log(Level.FINEST,"scan task failed: "+x,x);
493             else if (LOG.isLoggable(Level.FINE))
494                 LOG.log(Level.FINE,"scan task failed: "+x);
495             setStateAndNotify(STOPPED);
496         } catch (Error JavaDoc e) {
497             // We got an Error:
498
// Should not happen unless we ran out of memory or
499
// whatever - don't even try to notify, but
500
// stop the scan anyway!
501
//
502
state=STOPPED;
503             task.info = "Error: "+e;
504             
505             // rethrow error.
506
//
507
throw e;
508         }
509     }
510     
511     /**
512      * MBeanNotification support - delegates to broadcaster.
513      */

514     public void addNotificationListener(NotificationListener JavaDoc listener,
515             NotificationFilter JavaDoc filter, Object JavaDoc handback)
516             throws IllegalArgumentException JavaDoc {
517         broadcaster.addNotificationListener(listener, filter, handback);
518     }
519
520     // Switch this object state to the desired value an send
521
// a notification. Don't call this method from within a
522
// synchronized block!
523
//
524
private final void setStateAndNotify(ScanState desired) {
525         final ScanState old = state;
526         if (old == desired) return;
527         state = desired;
528         final AttributeChangeNotification JavaDoc n =
529                 new AttributeChangeNotification JavaDoc(this,
530                 getNextSeqNumber(),System.currentTimeMillis(),
531                 "state change","State",ScanState.class.getName(),
532                 String.valueOf(old),String.valueOf(desired));
533         broadcaster.sendNotification(n);
534     }
535     
536     
537     /**
538      * The {@link DirectoryScannerMXBean} may send two types of
539      * notifications: filematch, and state attribute changed.
540      **/

541     public MBeanNotificationInfo JavaDoc[] getNotificationInfo() {
542         return new MBeanNotificationInfo JavaDoc[] {
543             new MBeanNotificationInfo JavaDoc(
544                     new String JavaDoc[] {FILE_MATCHES_NOTIFICATION},
545                     Notification JavaDoc.class.getName(),
546                     "Emitted when a file that matches the scan criteria is found"
547                     ),
548             new MBeanNotificationInfo JavaDoc(
549                     new String JavaDoc[] {AttributeChangeNotification.ATTRIBUTE_CHANGE},
550                     AttributeChangeNotification JavaDoc.class.getName(),
551                     "Emitted when the State attribute changes"
552                     )
553         };
554     }
555
556     /**
557      * MBeanNotification support - delegates to broadcaster.
558      */

559     public void removeNotificationListener(NotificationListener JavaDoc listener)
560         throws ListenerNotFoundException JavaDoc {
561         broadcaster.removeNotificationListener(listener);
562     }
563
564     /**
565      * MBeanNotification support - delegates to broadcaster.
566      */

567     public void removeNotificationListener(NotificationListener JavaDoc listener,
568             NotificationFilter JavaDoc filter, Object JavaDoc handback)
569             throws ListenerNotFoundException JavaDoc {
570         broadcaster.removeNotificationListener(listener, filter, handback);
571     }
572     
573     // Validates the given root directory, returns a File object for
574
// that directory.
575
// Throws IllegalArgumentException if the given root is not
576
// acceptable.
577
//
578
private static File JavaDoc validateRoot(String JavaDoc root) {
579         if (root == null)
580             throw new IllegalArgumentException JavaDoc("no root specified");
581         if (root.length() == 0)
582             throw new IllegalArgumentException JavaDoc("specified root \"\" is invalid");
583         final File JavaDoc f = new File JavaDoc(root);
584         if (!f.canRead())
585             throw new IllegalArgumentException JavaDoc("can't read "+root);
586         if (!f.isDirectory())
587             throw new IllegalArgumentException JavaDoc("no such directory: "+root);
588         return f;
589     }
590
591 }
592
593
594
Popular Tags