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

Java example source code file (ScanManager.java)

This example Java source code file (ScanManager.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

can\'t, directoryscannermxbean, exception, failed, ioexception, jmexception, log, logging, management, map, objectname, scanmanagermxbean, scanstate, sessiontask, stopped, string, threading, threads, util

The ScanManager.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.ScanManagerMXBean.ScanState.*;
import com.sun.jmx.examples.scandir.ScanManagerMXBean.ScanState;
import com.sun.jmx.examples.scandir.config.DirectoryScannerConfig;
import com.sun.jmx.examples.scandir.config.ScanManagerConfig;
import java.io.File;

import java.io.IOException;
import java.lang.management.ManagementFactory;
import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.management.AttributeChangeNotification;
import javax.management.InstanceNotFoundException;
import javax.management.JMException;
import javax.management.JMX;
import javax.management.ListenerNotFoundException;
import javax.management.MBeanNotificationInfo;
import javax.management.MBeanRegistration;
import javax.management.MBeanServer;
import javax.management.MBeanServerConnection;
import javax.management.MalformedObjectNameException;
import javax.management.Notification;
import javax.management.NotificationBroadcasterSupport;
import javax.management.NotificationEmitter;
import javax.management.NotificationFilter;
import javax.management.NotificationListener;
import javax.management.ObjectInstance;
import javax.management.ObjectName;

/**
 * <p>
 * The <code>ScanManager is responsible for applying a configuration,
 * starting and scheduling directory scans, and reporting application state.
 * </p>
 * <p>
 * The ScanManager MBean is a singleton MBean which controls
 * scan session. The ScanManager name is defined by
 * {@link #SCAN_MANAGER_NAME ScanManager.SCAN_MANAGER_NAME}.
 * </p>
 * <p>
 * The <code>ScanManager MBean is the entry point of the scandir
 * application management interface. It is from this MBean that all other MBeans
 * will be created and registered.
 * </p>
 *
 * @author Sun Microsystems, 2006 - All rights reserved.
 */
public class ScanManager implements ScanManagerMXBean,
        NotificationEmitter, MBeanRegistration {

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

    /**
     * The name of the ScanManager singleton MBean.
     **/
    public final static ObjectName SCAN_MANAGER_NAME =
            makeSingletonName(ScanManagerMXBean.class);

    /**
     * Sequence number used for sending notifications. We use this
     * sequence number throughout the application.
     **/
    private static long seqNumber=0;

    /**
     * The NotificationBroadcasterSupport object used to handle
     * listener registration.
     **/
    private final NotificationBroadcasterSupport broadcaster;

    /**
     * The MBeanServer in which this MBean is registered. We obtain
     * this reference by implementing the {@link MBeanRegistration}
     * interface.
     **/
    private volatile MBeanServer mbeanServer;

    /**
     * A queue of pending notifications we are about to send.
     * We're using a BlockingQueue in order to avoid sending
     * notifications from within a synchronized block.
     **/
    private final BlockingQueue<Notification> pendingNotifs;

    /**
     * The state of the scan session.
     **/
    private volatile ScanState state = STOPPED;

    /**
     * The list of DirectoryScannerMBean that are run by a scan session.
     **/
    private final Map<ObjectName,DirectoryScannerMXBean> scanmap;

    /**
     * The list of ScanDirConfigMXBean that were created by this MBean.
     **/
    private final Map<ObjectName, ScanDirConfigMXBean> configmap;

    // The ResultLogManager for this application.
    private final ResultLogManager log;

    /**
     * We use a semaphore to ensure proper sequencing of exclusive
     * action. The logic we have implemented is to fail - rather
     * than block, if an exclusive action is already in progress.
     **/
    private final Semaphore sequencer = new Semaphore(1);

    // A proxy to the current ScanDirConfigMXBean which holds the current
    // configuration data.
    //
    private volatile ScanDirConfigMXBean config = null;

    // Avoid to write parameters twices when creating a new ConcurrentHashMap.
    //
    private static <K, V> Map newConcurrentHashMap() {
        return new ConcurrentHashMap<K, V>();
    }

    // Avoid to write parameters twices when creating a new HashMap.
    //
    private static <K, V> Map newHashMap() {
        return new HashMap<K, V>();
    }

    /**
     * Creates a default singleton ObjectName for a given class.
     * @param clazz The interface class of the MBean for which we want to obtain
     *        a default singleton name, or its implementation class.
     *        Give one or the other depending on what you wish to see in
     *        the value of the key {@code type=}.
     * @return A default singleton name for a singleton MBean class.
     * @throws IllegalArgumentException if the name can't be created
     *         for some unfathomable reason (e.g. an unexpected
     *         exception was raised).
     **/
    public final static ObjectName makeSingletonName(Class clazz) {
        try {
            final Package p = clazz.getPackage();
            final String packageName = (p==null)?null:p.getName();
            final String className   = clazz.getSimpleName();
            final String domain;
            if (packageName == null || packageName.length()==0) {
                // We use a reference to ScanDirAgent.class to ease
                // to keep track of possible class renaming.
                domain = ScanDirAgent.class.getSimpleName();
            } else {
                domain = packageName;
            }
            final ObjectName name = new ObjectName(domain,"type",className);
            return name;
        } catch (Exception x) {
            final IllegalArgumentException iae =
                    new IllegalArgumentException(String.valueOf(clazz),x);
            throw iae;
        }
    }

    /**
     * Creates a default ObjectName with keys <code>type= and
     * <code>name= for an instance of a given MBean interface class.
     * @param clazz The interface class of the MBean for which we want to obtain
     *        a default name, or its implementation class.
     *        Give one or the other depending on what you wish to see in
     *        the value of the key {@code type=}.
     * @param name The value of the <code>name= key.
     * @return A default name for an instance of the given MBean interface class.
     * @throws IllegalArgumentException if the name can't be created.
     *         (e.g. an unexpected exception was raised).
     **/
    public static final ObjectName makeMBeanName(Class clazz, String name) {
        try {
            return ObjectName.
                getInstance(makeSingletonName(clazz)
                        .toString()+",name="+name);
        } catch (MalformedObjectNameException x) {
            final IllegalArgumentException iae =
                    new IllegalArgumentException(String.valueOf(name),x);
            throw iae;
        }
    }

    /**
     * Return the ObjectName for a DirectoryScannerMXBean of that name.
     * This is {@code makeMBeanName(DirectoryScannerMXBean.class,name)}.
     * @param name The value of the <code>name= key.
     * @return the ObjectName for a DirectoryScannerMXBean of that name.
     */
    public static final ObjectName makeDirectoryScannerName(String name) {
        return makeMBeanName(DirectoryScannerMXBean.class,name);
    }

    /**
     * Return the ObjectName for a {@code ScanDirConfigMXBean} of that name.
     * This is {@code makeMBeanName(ScanDirConfigMXBean.class,name)}.
     * @param name The value of the <code>name= key.
     * @return the ObjectName for a {@code ScanDirConfigMXBean} of that name.
     */
    public static final ObjectName makeScanDirConfigName(String name) {
        return makeMBeanName(ScanDirConfigMXBean.class,name);
    }

    /**
     * Create and register a new singleton instance of the ScanManager
     * MBean in the given {@link MBeanServerConnection}.
     * @param mbs The MBeanServer in which the new singleton instance
     *         should be created.
     * @throws JMException The MBeanServer connection raised an exception
     *         while trying to instantiate and register the singleton MBean
     *         instance.
     * @throws IOException There was a connection problem while trying to
     *         communicate with the underlying MBeanServer.
     * @return A proxy for the registered MBean.
     **/
    public static ScanManagerMXBean register(MBeanServerConnection mbs)
        throws IOException, JMException {
        final ObjectInstance moi =
                mbs.createMBean(ScanManager.class.getName(),SCAN_MANAGER_NAME);
        final ScanManagerMXBean proxy =
                JMX.newMXBeanProxy(mbs,moi.getObjectName(),
                                  ScanManagerMXBean.class,true);
        return proxy;
    }

    /**
     * Creates a new {@code ScanManagerMXBean} proxy over the given
     * {@code MBeanServerConnection}. Does not check whether a
     * {@code ScanManagerMXBean}
     * is actually registered in that {@code MBeanServerConnection}.
     * @return a new {@code ScanManagerMXBean} proxy.
     * @param mbs The {@code MBeanServerConnection} which holds the
     * {@code ScanManagerMXBean} to proxy.
     */
    public static ScanManagerMXBean
            newSingletonProxy(MBeanServerConnection mbs) {
        final ScanManagerMXBean proxy =
                JMX.newMXBeanProxy(mbs,SCAN_MANAGER_NAME,
                                  ScanManagerMXBean.class,true);
        return proxy;
    }

    /**
     * Creates a new {@code ScanManagerMXBean} proxy over the platform
     * {@code MBeanServer}. This is equivalent to
     * {@code newSingletonProxy(ManagementFactory.getPlatformMBeanServer())}.
     * @return a new {@code ScanManagerMXBean} proxy.
     **/
    public static ScanManagerMXBean newSingletonProxy() {
        return newSingletonProxy(ManagementFactory.getPlatformMBeanServer());
    }

    /**
     * Create and register a new singleton instance of the ScanManager
     * MBean in the given {@link MBeanServerConnection}.
     * @throws JMException The MBeanServer connection raised an exception
     *         while trying to instantiate and register the singleton MBean
     *         instance.
     * @throws IOException There was a connection problem while trying to
     *         communicate with the underlying MBeanServer.
     * @return A proxy for the registered MBean.
     **/
    public static ScanManagerMXBean register()
        throws IOException, JMException {
        final MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
        return register(mbs);
    }

    /**
     * Create a new ScanManager MBean
     **/
    public ScanManager() {
        broadcaster = new NotificationBroadcasterSupport();
        pendingNotifs = new LinkedBlockingQueue<Notification>(100);
        scanmap = newConcurrentHashMap();
        configmap = newConcurrentHashMap();
        log = new ResultLogManager();
    }


    // Creates a new DirectoryScannerMXBean, from the given configuration data.
    DirectoryScannerMXBean createDirectoryScanner(DirectoryScannerConfig config) {
            return new DirectoryScanner(config,log);
    }

    // Applies a configuration.
    // throws IllegalStateException if lock can't be acquired.
    // Unregisters all existing directory scanners, the create and registers
    // new directory scanners according to the given config.
    // Then pushes the log config to the result log manager.
    //
    private void applyConfiguration(ScanManagerConfig bean)
        throws IOException, JMException {
        if (bean == null) return;
        if (!sequencer.tryAcquire()) {
            throw new IllegalStateException("Can't acquire lock");
        }
        try {
            unregisterScanners();
            final DirectoryScannerConfig[] scans = bean.getScanList();
            if (scans == null) return;
            for (DirectoryScannerConfig scan : scans) {
                addDirectoryScanner(scan);
            }
            log.setConfig(bean.getInitialResultLogConfig());
        } finally {
            sequencer.release();
        }
    }

    // See ScanManagerMXBean
    public void applyConfiguration(boolean fromMemory)
        throws IOException, JMException {
        if (fromMemory == false) config.load();
        applyConfiguration(config.getConfiguration());
    }

    // See ScanManagerMXBean
    public void applyCurrentResultLogConfig(boolean toMemory)
        throws IOException, JMException {
        final ScanManagerConfig bean = config.getConfiguration();
        bean.setInitialResultLogConfig(log.getConfig());
        config.setConfiguration(bean);
        if (toMemory==false) config.save();
    }

    // See ScanManagerMXBean
    public void setConfigurationMBean(ScanDirConfigMXBean config) {
        this.config = config;
    }

    // See ScanManagerMXBean
    public ScanDirConfigMXBean getConfigurationMBean() {
        return config;
    }

    // Creates and registers a new directory scanner.
    // Called by applyConfiguration.
    // throws IllegalStateException if state is not STOPPED or COMPLETED
    // (you cannot change the config while scanning is scheduled or running).
    //
    private DirectoryScannerMXBean addDirectoryScanner(
                DirectoryScannerConfig bean)
        throws JMException {
        try {
            final DirectoryScannerMXBean scanner;
            final ObjectName scanName;
            synchronized (this) {
                if (state != STOPPED && state != COMPLETED)
                   throw new IllegalStateException(state.toString());
                scanner = createDirectoryScanner(bean);
                scanName = makeDirectoryScannerName(bean.getName());
            }
            LOG.fine("server: "+mbeanServer);
            LOG.fine("scanner: "+scanner);
            LOG.fine("scanName: "+scanName);
            final ObjectInstance moi =
                mbeanServer.registerMBean(scanner,scanName);
            final ObjectName moiName = moi.getObjectName();
            final DirectoryScannerMXBean proxy =
                JMX.newMXBeanProxy(mbeanServer,moiName,
                DirectoryScannerMXBean.class,true);
            scanmap.put(moiName,proxy);
            return proxy;
        } catch (RuntimeException x) {
            final String msg = "Operation failed: "+x;
            if (LOG.isLoggable(Level.FINEST))
                LOG.log(Level.FINEST,msg,x);
            else LOG.fine(msg);
            throw x;
        } catch (JMException x) {
            final String msg = "Operation failed: "+x;
            if (LOG.isLoggable(Level.FINEST))
                LOG.log(Level.FINEST,msg,x);
            else LOG.fine(msg);
            throw x;
        }
    }

    // See ScanManagerMXBean
    public ScanDirConfigMXBean createOtherConfigurationMBean(String name,
            String filename)
        throws JMException {
        final ScanDirConfig profile = new ScanDirConfig(filename);
        final ObjectName profName = makeScanDirConfigName(name);
        final ObjectInstance moi = mbeanServer.registerMBean(profile,profName);
        final ScanDirConfigMXBean proxy =
                JMX.newMXBeanProxy(mbeanServer,profName,
                    ScanDirConfigMXBean.class,true);
        configmap.put(moi.getObjectName(),proxy);
        return proxy;
    }


    // See ScanManagerMXBean
    public Map<String,DirectoryScannerMXBean> getDirectoryScanners() {
        final Map<String,DirectoryScannerMXBean> proxyMap = newHashMap();
        for (Entry<ObjectName,DirectoryScannerMXBean> item : scanmap.entrySet()){
            proxyMap.put(item.getKey().getKeyProperty("name"),item.getValue());
        }
        return proxyMap;
    }

    // ---------------------------------------------------------------
    // State Management
    // ---------------------------------------------------------------

    /**
     * For each operation, this map stores a list of states from
     * which the corresponding operation can be legally called.
     * For instance, it is legal to call "stop" regardless of the
     * application state. However, "schedule" can be called only if
     * the application state is STOPPED, etc...
     **/
    private final static Map<String,EnumSet allowedStates;
    static {
        allowedStates = newHashMap();
        // You can always call stop
        allowedStates.put("stop",EnumSet.allOf(ScanState.class));

        // You can only call closed when stopped
        allowedStates.put("close",EnumSet.of(STOPPED,COMPLETED,CLOSED));

        // You can call schedule only when the current task is
        // completed or stopped.
        allowedStates.put("schedule",EnumSet.of(STOPPED,COMPLETED));

        // switch reserved for background task: goes from SCHEDULED to
        //    RUNNING when it enters the run() method.
        allowedStates.put("scan-running",EnumSet.of(SCHEDULED));

        // switch reserved for background task: goes from RUNNING to
        //    SCHEDULED when it has completed but needs to reschedule
        //    itself for specified interval.
        allowedStates.put("scan-scheduled",EnumSet.of(RUNNING));

        // switch reserved for background task:
        //     goes from RUNNING to COMPLETED upon successful completion
        allowedStates.put("scan-done",EnumSet.of(RUNNING));
    }

    // Get this object's state. No need to synchronize because
    // state is volatile.
    // See ScanManagerMXBean
    public ScanState getState() {
        return state;
    }

    /**
     * Enqueue a state changed notification for the given states.
     **/
    private void queueStateChangedNotification(
                    long sequence,
                    long time,
                    ScanState old,
                    ScanState current) {
        final AttributeChangeNotification n =
                new AttributeChangeNotification(SCAN_MANAGER_NAME,sequence,time,
                "ScanManager State changed to "+current,"State",
                ScanState.class.getName(),old.toString(),current.toString());
        // Queue the notification. We have created an unlimited queue, so
        // this method should always succeed.
        try {
            if (!pendingNotifs.offer(n,2,TimeUnit.SECONDS)) {
                LOG.fine("Can't queue Notification: "+n);
            }
        } catch (InterruptedException x) {
                LOG.fine("Can't queue Notification: "+x);
        }
    }

    /**
     * Send all notifications present in the queue.
     **/
    private void sendQueuedNotifications() {
        Notification n;
        while ((n = pendingNotifs.poll()) != null) {
            broadcaster.sendNotification(n);
        }
    }

    /**
     * Checks that the current state is allowed for the given operation,
     * and if so, switch its value to the new desired state.
     * This operation also enqueue the appropriate state changed
     * notification.
     **/
    private ScanState switchState(ScanState desired,String forOperation) {
        return switchState(desired,allowedStates.get(forOperation));
    }

    /**
     * Checks that the current state is one of the allowed states,
     * and if so, switch its value to the new desired state.
     * This operation also enqueue the appropriate state changed
     * notification.
     **/
    private ScanState switchState(ScanState desired,EnumSet<ScanState> allowed) {
        final ScanState old;
        final long timestamp;
        final long sequence;
        synchronized(this) {
            old = state;
            if (!allowed.contains(state))
               throw new IllegalStateException(state.toString());
            state = desired;
            timestamp = System.currentTimeMillis();
            sequence  = getNextSeqNumber();
        }
        LOG.fine("switched state: "+old+" -> "+desired);
        if (old != desired)
            queueStateChangedNotification(sequence,timestamp,old,desired);
        return old;
    }


    // ---------------------------------------------------------------
    // schedule() creates a new SessionTask that will be executed later
    // (possibly right away if delay=0) by a Timer thread.
    // ---------------------------------------------------------------

    // The timer used by this object. Lazzy evaluation. Cleaned in
    // postDeregister()
    //
    private Timer timer = null;

    // See ScanManagerMXBean
    public void schedule(long delay, long interval) {
        if (!sequencer.tryAcquire()) {
            throw new IllegalStateException("Can't acquire lock");
        }
        try {
            LOG.fine("scheduling new task: state="+state);
            final ScanState old = switchState(SCHEDULED,"schedule");
            final boolean scheduled =
                scheduleSession(new SessionTask(interval),delay);
            if (scheduled)
                LOG.fine("new task scheduled: state="+state);
        } finally {
            sequencer.release();
        }
        sendQueuedNotifications();
    }

    // Schedule a SessionTask. The session task may reschedule
    // a new identical task when it eventually ends.
    // We use this logic so that the 'interval' time is measured
    // starting at the end of the task that finishes, rather than
    // at its beginning. Therefore if a repeated task takes x ms,
    // it will be repeated every x+interval ms.
    //
    private synchronized boolean scheduleSession(SessionTask task, long delay) {
        if (state == STOPPED) return false;
        if (timer == null) timer = new Timer("ScanManager");
        tasklist.add(task);
        timer.schedule(task,delay);
        return true;
    }

    // ---------------------------------------------------------------
    // start() is equivalent to schedule(0,0)
    // ---------------------------------------------------------------

    // See ScanManagerMXBean
    public void start() throws IOException, InstanceNotFoundException {
        schedule(0,0);
    }

    // ---------------------------------------------------------------
    // Methods used to implement stop() -  stop() is asynchronous,
    // and needs to notify any running background task that it needs
    // to stop. It also needs to prevent scheduled task from being
    // run.
    // ---------------------------------------------------------------

    // See ScanManagerMXBean
    public void stop() {
        if (!sequencer.tryAcquire())
            throw new IllegalStateException("Can't acquire lock");
        int errcount = 0;
        final StringBuilder b = new StringBuilder();

        try {
            switchState(STOPPED,"stop");

            errcount += cancelSessionTasks(b);
            errcount += stopDirectoryScanners(b);
        } finally {
            sequencer.release();
        }

        sendQueuedNotifications();
        if (errcount > 0) {
            b.insert(0,"stop partially failed with "+errcount+" error(s):");
            throw new RuntimeException(b.toString());
        }
    }

    // See ScanManagerMXBean
    public void close() {
        switchState(CLOSED,"close");
        sendQueuedNotifications();
    }

    // Appends exception to a StringBuilder message.
    //
    private void append(StringBuilder b,String prefix,Throwable t) {
        final String first = (prefix==null)?"\n":"\n"+prefix;
        b.append(first).append(String.valueOf(t));
        Throwable cause = t;
        while ((cause = cause.getCause())!=null) {
            b.append(first).append("Caused by:").append(first);
            b.append('\t').append(String.valueOf(cause));
        }
    }

    // Cancels all scheduled session tasks
    //
    private int cancelSessionTasks(StringBuilder b) {
        int errcount = 0;
        // Stops scheduled tasks if any...
        //
        for (SessionTask task : tasklist) {
            try {
                task.cancel();
                tasklist.remove(task);
            } catch (Exception ex) {
                errcount++;
                append(b,"\t",ex);
            }
        }
        return errcount;
    }

    // Stops all DirectoryScanners configured for this object.
    //
    private int stopDirectoryScanners(StringBuilder b) {
        int errcount = 0;
        // Stops directory scanners if any...
        //
        for (DirectoryScannerMXBean s : scanmap.values()) {
            try {
                s.stop();
            } catch (Exception ex) {
                errcount++;
                append(b,"\t",ex);
            }
        }
        return errcount;
    }


    // ---------------------------------------------------------------
    // We start scanning in background in a Timer thread.
    // The methods below implement that logic.
    // ---------------------------------------------------------------

    private void scanAllDirectories()
        throws IOException, InstanceNotFoundException {

        int errcount = 0;
        final StringBuilder b = new StringBuilder();
        for (ObjectName key : scanmap.keySet()) {
            final DirectoryScannerMXBean s = scanmap.get(key);
            try {
                if (state == STOPPED) return;
                s.scan();
            } catch (Exception ex) {
                LOG.log(Level.FINE,key + " failed to scan: "+ex,ex);
                errcount++;
                append(b,"\t",ex);
            }
        }
        if (errcount > 0) {
            b.insert(0,"scan partially performed with "+errcount+" error(s):");
            throw new RuntimeException(b.toString());
        }
    }

    // List of scheduled session task. Needed by stop() to cancel
    // scheduled sessions. There's usually at most 1 session in
    // this list (unless there's a bug somewhere ;-))
    //
    private final ConcurrentLinkedQueue<SessionTask> tasklist =
            new ConcurrentLinkedQueue<SessionTask>();

    // Used to give a unique id to session task - useful for
    // debugging.
    //
    private volatile static long taskcount = 0;

    /**
     * A session task will be scheduled to run in background in a
     * timer thread. There can be at most one session task running
     * at a given time (this is ensured by using a timer - which is
     * a single threaded object).
     *
     * If the session needs to be repeated, it will reschedule an
     * identical session when it finishes to run. This ensure that
     * two session runs are separated by the given interval time.
     *
     **/
    private class SessionTask extends TimerTask {

        /**
         * Delay after which the next iteration of this task will
         * start. This delay is measured  starting at the end of
         * the previous iteration.
         **/
        final long delayBeforeNext;

        /**
         * A unique id for this task.
         **/
        final long taskid;

        /**
         * Whether it's been cancelled by stop()
         **/
        volatile boolean cancelled=false;

        /**
         * create a new SessionTask.
         **/
        SessionTask(long scheduleNext) {
            delayBeforeNext = scheduleNext;
            taskid = taskcount++;
        }

        /**
         * When run() begins, the state is switched to RUNNING.
         * When run() ends then:
         *      If the task is repeated, the state will be switched
         *      to SCHEDULED (because a new task was scheduled).
         *      Otherwise the state will be switched to either
         *      STOPPED (if it was stopped before it could complete)
         *      or COMPLETED (if it completed gracefully)
         * This method is used to switch to the desired state and
         * send the appropriate notifications.
         * When entering the method, we check whether the state is
         * STOPPED. If so, we return false - and the SessionTask will
         * stop. Otherwise, we switch the state to the desired value.
         **/
        private boolean notifyStateChange(ScanState newState,String condition) {
            synchronized (ScanManager.this) {
                if (state == STOPPED || state == CLOSED) return false;
                switchState(newState,condition);
            }
            sendQueuedNotifications();
            return true;
        }

        // Cancels this task.
        public boolean cancel() {
            cancelled=true;
            return super.cancel();
        }

        /**
         * Invoke all directories scanners in sequence. At each
         * step, checks to see whether the task should stop.
         **/
        private boolean execute() {
            final String tag = "Scheduled session["+taskid+"]";
            try {
                if (cancelled) {
                    LOG.finer(tag+" cancelled: done");
                    return false;
                }
                if (!notifyStateChange(RUNNING,"scan-running")) {
                    LOG.finer(tag+" stopped: done");
                    return false;
                }
                scanAllDirectories();
            } catch (Exception x) {
                if (LOG.isLoggable(Level.FINEST)) {
                    LOG.log(Level.FINEST,
                            tag+" failed to scan: "+x,x);
                } else if (LOG.isLoggable(Level.FINE)) {
                    LOG.fine(tag+" failed to scan: "+x);
                }
            }
            return true;
        }

        /**
         * Schedule an identical task for next iteration.
         **/
        private boolean scheduleNext() {
            final String tag = "Scheduled session["+taskid+"]";

            // We need now to reschedule a new task for after 'delayBeforeNext' ms.
            try {
                LOG.finer(tag+": scheduling next session for "+ delayBeforeNext + "ms");
                if (cancelled || !notifyStateChange(SCHEDULED,"scan-scheduled")) {
                    LOG.finer(tag+" stopped: do not reschedule");
                    return false;
                }
                final SessionTask nextTask = new SessionTask(delayBeforeNext);
                if (!scheduleSession(nextTask,delayBeforeNext)) return false;
                LOG.finer(tag+": next session successfully scheduled");
            } catch (Exception x) {
                if (LOG.isLoggable(Level.FINEST)) {
                    LOG.log(Level.FINEST,tag+
                            " failed to schedule next session: "+x,x);
                } else if (LOG.isLoggable(Level.FINE)) {
                    LOG.fine(tag+" failed to schedule next session: "+x);
                }
            }
            return true;
        }


        /**
         * The run method:
         * executes scanning logic, the schedule next iteration if needed.
         **/
        public void run() {
            final String tag = "Scheduled session["+taskid+"]";
            LOG.entering(SessionTask.class.getName(),"run");
            LOG.finer(tag+" starting...");
            try {
                if (execute()==false) return;

                LOG.finer(tag+" terminating - state is "+state+
                    ((delayBeforeNext >0)?(" next session is due in "+delayBeforeNext+" ms."):
                        " no additional session scheduled"));

                // if delayBeforeNext <= 0 we are done, either because the session was
                // stopped or because it successfully completed.
                if (delayBeforeNext <= 0) {
                    if (!notifyStateChange(COMPLETED,"scan-done"))
                        LOG.finer(tag+" stopped: done");
                    else
                        LOG.finer(tag+" completed: done");
                    return;
                }

                // we need to reschedule a new session for 'delayBeforeNext' ms.
                scheduleNext();

            } finally {
                tasklist.remove(this);
                LOG.finer(tag+" finished...");
                LOG.exiting(SessionTask.class.getName(),"run");
            }
        }
    }

    // ---------------------------------------------------------------
    // ---------------------------------------------------------------

    // ---------------------------------------------------------------
    // MBean Notification support
    // The methods below are imported from {@link NotificationEmitter}
    // ---------------------------------------------------------------

    /**
     * Delegates the implementation of this method to the wrapped
     * {@code NotificationBroadcasterSupport} object.
     **/
    public void addNotificationListener(NotificationListener listener, NotificationFilter filter, Object handback) throws IllegalArgumentException {
        broadcaster.addNotificationListener(listener, filter, handback);
    }


    /**
     * We emit an {@code AttributeChangeNotification} when the {@code State}
     * attribute changes.
     **/
    public MBeanNotificationInfo[] getNotificationInfo() {
        return new MBeanNotificationInfo[] {
            new MBeanNotificationInfo(new String[] {
                AttributeChangeNotification.ATTRIBUTE_CHANGE},
                AttributeChangeNotification.class.getName(),
                "Emitted when the State attribute changes")
            };
    }

    /**
     * Delegates the implementation of this method to the wrapped
     * {@code NotificationBroadcasterSupport} object.
     **/
    public void removeNotificationListener(NotificationListener listener) throws ListenerNotFoundException {
        broadcaster.removeNotificationListener(listener);
    }

    /**
     * Delegates the implementation of this method to the wrapped
     * {@code NotificationBroadcasterSupport} object.
     **/
    public void removeNotificationListener(NotificationListener listener, NotificationFilter filter, Object handback) throws ListenerNotFoundException {
        broadcaster.removeNotificationListener(listener, filter, handback);
    }

    /**
     * Returns and increment the sequence number used for
     * notifications. We use the same sequence number throughout the
     * application - this is why this method is only package protected.
     * @return A unique sequence number for the next notification.
     */
    static synchronized long getNextSeqNumber() {
        return seqNumber++;
    }

    // ---------------------------------------------------------------
    // End of MBean Notification support
    // ---------------------------------------------------------------

    // ---------------------------------------------------------------
    // MBeanRegistration support
    // The methods below are imported from {@link MBeanRegistration}
    // ---------------------------------------------------------------

    /**
     * Allows the MBean to perform any operations it needs before being
     * registered in the MBean server. If the name of the MBean is not
     * specified, the MBean can provide a name for its registration. If
     * any exception is raised, the MBean will not be registered in the
     * MBean server.
     * <p>In this implementation, we check that the provided name is
     * either {@code null} or equals to {@link #SCAN_MANAGER_NAME}. If it
     * isn't then we throw an IllegalArgumentException, otherwise we return
     * {@link #SCAN_MANAGER_NAME}.</p>
     * <p>This ensures that there will be a single instance of ScanManager
     * registered in a given MBeanServer, and that it will always be
     * registered with the singleton's {@link #SCAN_MANAGER_NAME}.</p>
     * <p>We do not need to check whether an MBean by that name is
     *    already registered because the MBeanServer will perform
     *    this check just after having called preRegister().</p>
     * @param server The MBean server in which the MBean will be registered.
     * @param name The object name of the MBean. This name is null if the
     * name parameter to one of the createMBean or registerMBean methods in
     * the MBeanServer interface is null. In that case, this method must
     * return a non-null ObjectName for the new MBean.
     * @return The name under which the MBean is to be registered. This value
     * must not be null. If the name parameter is not null, it will usually
     * but not necessarily be the returned value.
     * @throws Exception This exception will be caught by the MBean server and
     * re-thrown as an MBeanRegistrationException.
     */
    public ObjectName preRegister(MBeanServer server, ObjectName name) throws Exception {
        if (name != null) {
            if (!SCAN_MANAGER_NAME.equals(name))
                throw new IllegalArgumentException(String.valueOf(name));
        }
        mbeanServer = server;
        return SCAN_MANAGER_NAME;
    }

    // Returns the default configuration filename
    static String getDefaultConfigurationFileName() {
        // This is a file calles 'jmx-scandir.xml' located
        // in the user directory.
        final String user = System.getProperty("user.home");
        final String defconf = user+File.separator+"jmx-scandir.xml";
        return defconf;
    }

    /**
     * Allows the MBean to perform any operations needed after having
     * been registered in the MBean server or after the registration has
     * failed.
     * <p>
     * If registration was not successful, the method returns immediately.
     * <p>
     * If registration is successful, register the {@link ResultLogManager}
     * and default {@link ScanDirConfigMXBean}. If registering these
     * MBean fails, the {@code ScanManager} state will be switched to
     * {@link #close CLOSED}, and postRegister ends there.
     * </p>
     * <p>Otherwise the {@code ScanManager} will ask the
     * {@link ScanDirConfigMXBean} to load its configuration.
     * If it succeeds, the configuration will be {@link
     * #applyConfiguration applied}. Otherwise, the method simply returns,
     * assuming that the user will later create/update a configuration and
     * apply it.
     * @param registrationDone Indicates whether or not the MBean has been
     * successfully registered in the MBean server. The value false means
     * that the registration has failed.
     */
    public void postRegister(Boolean registrationDone) {
        if (!registrationDone) return;
        Exception test=null;
        try {
            mbeanServer.registerMBean(log,
                    ResultLogManager.RESULT_LOG_MANAGER_NAME);
            final String defconf = getDefaultConfigurationFileName();
            final String conf = System.getProperty("scandir.config.file",defconf);
            final String confname = ScanDirConfig.guessConfigName(conf,defconf);
            final ObjectName defaultProfileName =
                    makeMBeanName(ScanDirConfigMXBean.class,confname);
            if (!mbeanServer.isRegistered(defaultProfileName))
                mbeanServer.registerMBean(new ScanDirConfig(conf),
                        defaultProfileName);
            config = JMX.newMXBeanProxy(mbeanServer,defaultProfileName,
                    ScanDirConfigMXBean.class,true);
            configmap.put(defaultProfileName,config);
        } catch (Exception x) {
            LOG.config("Failed to populate MBeanServer: "+x);
            close();
            return;
        }
        try {
            config.load();
        } catch (Exception x) {
            LOG.finest("No config to load: "+x);
            test = x;
        }
        if (test == null) {
            try {
                applyConfiguration(config.getConfiguration());
            } catch (Exception x) {
                if (LOG.isLoggable(Level.FINEST))
                    LOG.log(Level.FINEST,"Failed to apply config: "+x,x);
                LOG.config("Failed to apply config: "+x);
            }
        }
    }

    // Unregisters all created DirectoryScanners
    private void unregisterScanners() throws JMException {
        unregisterMBeans(scanmap);
    }

    // Unregisters all created ScanDirConfigs
    private void unregisterConfigs() throws JMException {
        unregisterMBeans(configmap);
    }

    // Unregisters all MBeans named by the given map
    private void unregisterMBeans(Map<ObjectName,?> map) throws JMException {
        for (ObjectName key : map.keySet()) {
            if (mbeanServer.isRegistered(key))
                mbeanServer.unregisterMBean(key);
            map.remove(key);
        }
    }

    // Unregisters the ResultLogManager.
    private void unregisterResultLogManager() throws JMException {
        final ObjectName name = ResultLogManager.RESULT_LOG_MANAGER_NAME;
        if (mbeanServer.isRegistered(name)) {
            mbeanServer.unregisterMBean(name);
        }
    }

    /**
     * Allows the MBean to perform any operations it needs before being
     * unregistered by the MBean server.
     * This implementation also unregisters all the MXBeans
     * that were created by this object.
     * @throws IllegalStateException if the lock can't be acquire, or if
     *         the MBean's state doesn't allow the MBean to be unregistered
     *         (e.g. because it's scheduled or running).
     * @throws Exception This exception will be caught by the MBean server and
     * re-thrown as an MBeanRegistrationException.
     */
    public void preDeregister() throws Exception {
        try {
            close();
            if (!sequencer.tryAcquire())
                throw new IllegalStateException("can't acquire lock");
            try {
                unregisterScanners();
                unregisterConfigs();
                unregisterResultLogManager();
            } finally {
                sequencer.release();
            }
        } catch (Exception x) {
            LOG.log(Level.FINEST,"Failed to unregister: "+x,x);
            throw x;
        }
    }

    /**
     * Allows the MBean to perform any operations needed after having been
     * unregistered in the MBean server.
     * Cancels the internal timer - if any.
     */
    public synchronized void postDeregister() {
        if (timer != null) {
            try {
                timer.cancel();
            } catch (Exception x) {
                if (LOG.isLoggable(Level.FINEST))
                    LOG.log(Level.FINEST,"Failed to cancel timer",x);
                else if (LOG.isLoggable(Level.FINE))
                    LOG.fine("Failed to cancel timer: "+x);
            } finally {
                timer = null;
            }
        }
   }

    // ---------------------------------------------------------------
    // End of MBeanRegistration support
    // ---------------------------------------------------------------

}

Other Java examples (source code examples)

Here is a short list of links related to this Java ScanManager.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.