/* * Copyright 2006 Sun Microsystems, Inc. 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 Sun Microsystems 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. */ 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</code> 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</code> MBean is the entry point of the <i>scandir</i> * 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<K, V> newConcurrentHashMap() { return new ConcurrentHashMap<K, V>(); } // Avoid to write parameters twices when creating a new HashMap. // private static <K, V> Map<K, V> 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=</code> and * <code>name=</code> 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=</code> 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=</code> 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=</code> 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<ScanState>> 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 // --------------------------------------------------------------- }