alvinalexander.com | career | drupal | java | mac | mysql | perl | scala | uml | unix  

Java example source code file (DirectoryScanner.java)

This example Java source code file (DirectoryScanner.java) is included in the alvinalexander.com "Java Source Code Warehouse" project. The intent of this project is to help you "Learn Java by Example" TM.

Learn more about this Java project at its project page.

Java - Java tags/keywords

attributechangenotification, directoryscanner, directoryscannerconfig, error, failed, file, file_matches_notification, illegalargumentexception, linkedlist, listenernotfoundexception, log, logging, management, mbeannotificationinfo, scanstate, scantask, string, util

The DirectoryScanner.java Java example source code

/*
 * Copyright (c) 2006, 2011, Oracle and/or its affiliates. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *   - Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *
 *   - Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *
 *   - Neither the name of Oracle nor the names of its
 *     contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
 * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

/*
 * This source code is provided to illustrate the usage of a given feature
 * or technique and has been deliberately simplified. Additional steps
 * required for a production-quality application, such as security checks,
 * input validation and proper error handling, might not be present in
 * this sample code.
 */


package com.sun.jmx.examples.scandir;

import static com.sun.jmx.examples.scandir.ScanManager.getNextSeqNumber;
import com.sun.jmx.examples.scandir.ScanManagerMXBean.ScanState;
import static com.sun.jmx.examples.scandir.ScanManagerMXBean.ScanState.*;
import static com.sun.jmx.examples.scandir.config.DirectoryScannerConfig.Action.*;
import com.sun.jmx.examples.scandir.config.XmlConfigUtils;
import com.sun.jmx.examples.scandir.config.DirectoryScannerConfig;
import com.sun.jmx.examples.scandir.config.DirectoryScannerConfig.Action;
import com.sun.jmx.examples.scandir.config.ResultRecord;
import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.management.AttributeChangeNotification;
import javax.management.InstanceNotFoundException;
import javax.management.ListenerNotFoundException;
import javax.management.MBeanNotificationInfo;
import javax.management.Notification;
import javax.management.NotificationBroadcasterSupport;
import javax.management.NotificationEmitter;
import javax.management.NotificationFilter;
import javax.management.NotificationListener;

/**
 * A <code>DirectoryScanner is an MBean that
 * scans a file system starting at a given root directory,
 * and then looks for files that match a given criteria.
 * <p>
 * When such a file is found, the <code>DirectoryScanner takes
 * the action for which it was configured: emit a notification,
 * <i>and or log a {@link
 * com.sun.jmx.examples.scandir.config.ResultRecord} for this file,
 * <i>and or delete that file.
 * </p>
 * <p>
 * The code that would actually delete the file is commented out - so that
 * nothing valuable is lost if this example is run by mistake on the wrong
 * set of directories.<br>
 * Logged results are logged by sending them to the {@link ResultLogManager}.
 * </p>
 * <p>
 * <code>DirectoryScannerMXBeans are created, initialized, and
 * registered by the {@link ScanManagerMXBean}.
 * The {@link ScanManagerMXBean} will also schedule and run them in
 * background by calling their {@link #scan} method.
 * </p>
 * <p>Client code is not expected to create or register directly any such
 * MBean. Instead, clients are expected to modify the configuration, using
 * the {@link ScanDirConfigMXBean}, and then apply it, using the {@link
 * ScanManagerMXBean}. Instances of <code>DirectoryScannerMXBeans
 * will then be created and registered (or unregistered and garbage collected)
 * as a side effect of applying that configuration.
 * </p>
 *
 * @author Sun Microsystems, 2006 - All rights reserved.
 */
public class DirectoryScanner implements
        DirectoryScannerMXBean, NotificationEmitter {

    /**
     * The type for <i>com.sun.jmx.examples.scandir.filematch notifications.
     * Notifications of this type will be emitted whenever a file that
     * matches this {@code DirectoryScanner} criteria is found, but only if
     * this {@code DirectoryScanner} was configured to {@link
     * Action#NOTIFY notify} for matching files.
     **/
    public static final String FILE_MATCHES_NOTIFICATION =
            "com.sun.jmx.examples.scandir.filematch";

    /**
     * A logger for this class.
     **/
    private static final Logger LOG =
            Logger.getLogger(DirectoryScanner.class.getName());

    // Attribute : State
    //
    private volatile ScanState state = STOPPED;

    // The DirectoryScanner delegates the implementation of
    // the NotificationEmitter interface to a wrapped instance
    // of NotificationBroadcasterSupport.
    //
    private final NotificationBroadcasterSupport broadcaster;

    // The root directory at which this DirectoryScanner will start
    // scanning. Constructed from config.getRootDirectory().
    //
    private final File rootFile;

    // This DirectoryScanner config - this is a constant which is
    // provided at construction time by the {@link ScanManager}.
    //
    private final DirectoryScannerConfig config;

    // The set of actions for which this DirectoryScanner is configured.
    // Constructed from config.getActions()
    //
    final Set<Action> actions;

    // The ResultLogManager that this DirectoryScanner will use to log
    // info. This is a hard reference to another MBean, provided
    // at construction time by the ScanManager.
    // The ScanManager makes sure that the life cycle of these two MBeans
    // is consistent.
    //
    final ResultLogManager logManager;

    /**
     * Constructs a new {@code DirectoryScanner}.
     * <p>This constructor is
     * package protected, and this MBean cannot be created by a remote
     * client, because it needs a reference to the {@link ResultLogManager},
     * which cannot be provided from remote.
     * </p>
     * <p>This is a conscious design choice: {@code DirectoryScanner} MBeans
     * are expected to be completely managed (created, registered, unregistered)
     * by the {@link ScanManager} which does provide this reference.
     * </p>
     *
     * @param config This {@code DirectoryScanner} configuration.
     * @param logManager The info log manager with which to log the info
     *        records.
     * @throws IllegalArgumentException if one of the parameter is null, or if
     *         the provided {@code config} doesn't have its {@code name} set,
     *         or if the {@link DirectoryScannerConfig#getRootDirectory
     *         root directory} provided in the {@code config} is not acceptable
     *         (not provided or not found or not readable, etc...).
     **/
    public DirectoryScanner(DirectoryScannerConfig config,
                            ResultLogManager logManager)
        throws IllegalArgumentException {
        if (logManager == null)
            throw new IllegalArgumentException("log=null");
        if (config == null)
            throw new IllegalArgumentException("config=null");
        if (config.getName() == null)
            throw new IllegalArgumentException("config.name=null");

         broadcaster = new NotificationBroadcasterSupport();

         // Clone the config: ensure data encapsulation.
         //
         this.config = XmlConfigUtils.xmlClone(config);

         // Checks that the provided root directory is valid.
         // Throws IllegalArgumentException if it isn't.
         //
         rootFile = validateRoot(config.getRootDirectory());

         // Initialize the Set<Action> for which this DirectoryScanner
         // is configured.
         //
         if (config.getActions() == null)
             actions = Collections.emptySet();
         else
             actions = EnumSet.copyOf(Arrays.asList(config.getActions()));
         this.logManager = logManager;
    }

    // see DirectoryScannerMXBean
    public void stop() {
        // switch state to stop and send AttributeValueChangeNotification
        setStateAndNotify(STOPPED);
    }

    // see DirectoryScannerMXBean
    public String getRootDirectory() {
        return rootFile.getAbsolutePath();
    }


    // see DirectoryScannerMXBean
    public ScanState getState() {
        return state;
    }

    // see DirectoryScannerMXBean
    public DirectoryScannerConfig getConfiguration() {
        return config;
    }

    // see DirectoryScannerMXBean
    public String getCurrentScanInfo() {
        final ScanTask currentOrLastTask = currentTask;
        if (currentOrLastTask == null) return "Never Run";
        return currentOrLastTask.getScanInfo();
    }

    // This variable points to the current (or latest) scan.
    //
    private volatile ScanTask currentTask = null;

    // see DirectoryScannerMXBean
    public void scan() {
        final ScanTask task;

        synchronized (this) {
            final LinkedList<File> list;
            switch (state) {
                case RUNNING:
                case SCHEDULED:
                    throw new IllegalStateException(state.toString());
                case STOPPED:
                case COMPLETED:
                    // only accept to scan if state is STOPPED or COMPLETED.
                    list = new LinkedList<File>();
                    list.add(rootFile);
                    break;
                default:
                    throw new IllegalStateException(String.valueOf(state));
            }

            // Create a new ScanTask object for our root directory file.
            //
            currentTask = task = new ScanTask(list,this);

            // transient state... will be switched to RUNNING when
            // task.execute() is called. This code could in fact be modified
            // to use java.util.concurent.Future and, to push the task to
            // an executor. We would then need to wait for the task to
            // complete before returning.  However, this wouldn't buy us
            // anything - since this method should wait for the task to
            // finish anyway: so why would we do it?
            // As it stands, we simply call task.execute() in the current
            // thread - brave and fearless readers may want to attempt the
            // modification ;-)
            //
            setStateAndNotify(SCHEDULED);
        }
        task.execute();
    }

    // This method is invoked to carry out the configured actions on a
    // matching file.
    // Do not call this method from within synchronized() { } as this
    // method may send notifications!
    //
    void actOn(File file) {

        // Which action were actually taken
        //
        final Set<Action> taken = new HashSet();
        boolean logresult = false;

        // Check out which actions are configured and carry them out.
        //
        for (Action action : actions) {
            switch (action) {
                case DELETE:
                    if (deleteFile(file)) {
                        // Delete succeeded: add DELETE to the set of
                        // actions carried out.
                        taken.add(DELETE);
                    }
                    break;
                case NOTIFY:
                    if (notifyMatch(file)) {
                        // Notify succeeded: add NOTIFY to the set of
                        // actions carried out.
                        taken.add(NOTIFY);
                    }
                    break;
                case LOGRESULT:
                    // LOGRESULT was configured - log actions carried out.
                    // => we must execute this action as the last action.
                    //    simply set logresult=true for now. We will do
                    //    the logging later
                    logresult = true;
                    break;
                default:
                    LOG.fine("Failed to execute action: " +action +
                            " - action not supported");
                    break;
            }
        }

        // Now is time for logging:
        if (logresult) {
            taken.add(LOGRESULT);
            if (!logResult(file,taken.toArray(new Action[taken.size()])))
                taken.remove(LOGRESULT); // just for the last trace below...
        }

        LOG.finest("File processed: "+taken+" - "+file.getAbsolutePath());
    }

    // Deletes a matching file.
    private boolean deleteFile(File file) {
        try {
            // file.delete() is commented so that we don't do anything
            // bad if the example is mistakenly run on the wrong set of
            // directories.
            //
            /* file.delete(); */
            System.out.println("DELETE not implemented for safety reasons.");
            return true;
        } catch (Exception x) {
            LOG.fine("Failed to delete: "+file.getAbsolutePath());
        }
        return false;
    }

    // Notifies of a matching file.
    private boolean notifyMatch(File file) {
        try {
            final Notification n =
                    new Notification(FILE_MATCHES_NOTIFICATION,this,
                    getNextSeqNumber(),
                    file.getAbsolutePath());

            // This method *is not* called from any synchronized block, so
            // we can happily call broadcaster.sendNotification() here.
            // Note that verifying whether a method is called from within
            // a synchronized block demends a thoroughful code reading,
            // examining each of the 'parent' methods in turn.
            //
            broadcaster.sendNotification(n);
            return true;
        } catch (Exception x) {
            LOG.fine("Failed to notify: "+file.getAbsolutePath());
        }
        return false;
    }

    // Logs a result with the ResultLogManager
    private boolean logResult(File file,Action[] actions) {
        try {
            logManager.log(new ResultRecord(config, actions,file));
            return true;
        } catch (Exception x) {
            LOG.fine("Failed to log: "+file.getAbsolutePath());
        }
        return false;
    }


    // Contextual object used to store info about current
    // (or last) scan.
    //
    private static class ScanTask {

        // List of Files that remain to scan.
        // When files are discovered they are added to the list.
        // When they are being handled, they are removed from the list.
        // When the list is empty, the scanning is finished.
        //
        private final LinkedList<File>   list;
        private final DirectoryScanner scan;

        // Some statistics...
        //
        private volatile long scanned=0;
        private volatile long matching=0;

        private volatile String info="Not started";

        ScanTask(LinkedList<File> list, DirectoryScanner scan) {
            this.list = list; this.scan = scan;
        }

        public void execute() {
            scan(list);
        }

        private void scan(LinkedList<File> list) {
             scan.scan(this,list);
        }

        public String getScanInfo() {
            return info+" - ["+scanned+" scanned, "+matching+" matching]";
        }
    }

    // The actual scan logic. Switches state to RUNNING,
    // and scan the list of given dirs.
    // The list is a live object which is updated by this method.
    // This would allow us to implement methods like "pause" and "resume",
    // since all the info needed to resume would be in the list.
    //
    private void scan(ScanTask task, LinkedList<File> list) {
        setStateAndNotify(RUNNING);
        task.info = "In Progress";
        try {

            // The FileFilter will tell us which files match and which don't.
            //
            final FileFilter filter = config.buildFileFilter();

            // We have two condition to end the loop: either the list is
            // empty, meaning there's nothing more to scan, or the state of
            // the DirectoryScanner was asynchronously switched to STOPPED by
            // another thread, e.g. because someone called "stop" on the
            // ScanManagerMXBean
            //
            while (!list.isEmpty() && state == RUNNING) {

                // Get and remove the first element in the list.
                //
                final File current = list.poll();

                // Increment number of file scanned.
                task.scanned++;

                // If 'current' is a file, it's already been matched by our
                // file filter (see below): act on it.
                // Note that for the first iteration of this loop, there will
                // be one single file in the list: the root directory for this
                // scanner.
                //
                if (current.isFile()) {
                    task.matching++;
                    actOn(current);
                }

                // If 'current' is a directory, then
                // find files and directories that match the file filter
                // in this directory
                //
                if (current.isDirectory()) {

                    // Gets matching files and directories
                    final File[] content = current.listFiles(filter);
                    if (content == null) continue;

                    // Adds all matching file to the list.
                    list.addAll(0,Arrays.asList(content));
                }
            }

            // The loop terminated. If the list is empty, then we have
            // completed our task. If not, then somebody must have called
            // stop() on this directory scanner.
            //
            if (list.isEmpty()) {
                task.info = "Successfully Completed";
                setStateAndNotify(COMPLETED);
            }
        } catch (Exception x) {
            // We got an exception: stop the scan
            //
            task.info = "Failed: "+x;
            if (LOG.isLoggable(Level.FINEST))
                LOG.log(Level.FINEST,"scan task failed: "+x,x);
            else if (LOG.isLoggable(Level.FINE))
                LOG.log(Level.FINE,"scan task failed: "+x);
            setStateAndNotify(STOPPED);
        } catch (Error e) {
            // We got an Error:
            // Should not happen unless we ran out of memory or
            // whatever - don't even try to notify, but
            // stop the scan anyway!
            //
            state=STOPPED;
            task.info = "Error: "+e;

            // rethrow error.
            //
            throw e;
        }
    }

    /**
     * MBeanNotification support - delegates to broadcaster.
     */
    public void addNotificationListener(NotificationListener listener,
            NotificationFilter filter, Object handback)
            throws IllegalArgumentException {
        broadcaster.addNotificationListener(listener, filter, handback);
    }

    // Switch this object state to the desired value an send
    // a notification. Don't call this method from within a
    // synchronized block!
    //
    private final void setStateAndNotify(ScanState desired) {
        final ScanState old = state;
        if (old == desired) return;
        state = desired;
        final AttributeChangeNotification n =
                new AttributeChangeNotification(this,
                getNextSeqNumber(),System.currentTimeMillis(),
                "state change","State",ScanState.class.getName(),
                String.valueOf(old),String.valueOf(desired));
        broadcaster.sendNotification(n);
    }


    /**
     * The {@link DirectoryScannerMXBean} may send two types of
     * notifications: filematch, and state attribute changed.
     **/
    public MBeanNotificationInfo[] getNotificationInfo() {
        return new MBeanNotificationInfo[] {
            new MBeanNotificationInfo(
                    new String[] {FILE_MATCHES_NOTIFICATION},
                    Notification.class.getName(),
                    "Emitted when a file that matches the scan criteria is found"
                    ),
            new MBeanNotificationInfo(
                    new String[] {AttributeChangeNotification.ATTRIBUTE_CHANGE},
                    AttributeChangeNotification.class.getName(),
                    "Emitted when the State attribute changes"
                    )
        };
    }

    /**
     * MBeanNotification support - delegates to broadcaster.
     */
    public void removeNotificationListener(NotificationListener listener)
        throws ListenerNotFoundException {
        broadcaster.removeNotificationListener(listener);
    }

    /**
     * MBeanNotification support - delegates to broadcaster.
     */
    public void removeNotificationListener(NotificationListener listener,
            NotificationFilter filter, Object handback)
            throws ListenerNotFoundException {
        broadcaster.removeNotificationListener(listener, filter, handback);
    }

    // Validates the given root directory, returns a File object for
    // that directory.
    // Throws IllegalArgumentException if the given root is not
    // acceptable.
    //
    private static File validateRoot(String root) {
        if (root == null)
            throw new IllegalArgumentException("no root specified");
        if (root.length() == 0)
            throw new IllegalArgumentException("specified root \"\" is invalid");
        final File f = new File(root);
        if (!f.canRead())
            throw new IllegalArgumentException("can't read "+root);
        if (!f.isDirectory())
            throw new IllegalArgumentException("no such directory: "+root);
        return f;
    }

}

Other Java examples (source code examples)

Here is a short list of links related to this Java DirectoryScanner.java source code file:

... this post is sponsored by my books ...

#1 New Release!

FP Best Seller

 

new blog posts

 

Copyright 1998-2021 Alvin Alexander, alvinalexander.com
All Rights Reserved.

A percentage of advertising revenue from
pages under the /java/jwarehouse URI on this website is
paid back to open source projects.