| /* |
| * 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</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 |
| // --------------------------------------------------------------- |
| |
| } |