org.helios.jzab.agent.net.active.ActiveHost.java Source code

Java tutorial

Introduction

Here is the source code for org.helios.jzab.agent.net.active.ActiveHost.java

Source

/**
 * Helios, OpenSource Monitoring
 * Brought to you by the Helios Development Group
 *
 * Copyright 2007, Helios Development Group and individual contributors
 * as indicated by the @author tags. See the copyright.txt file in the
 * distribution for a full listing of individual contributors.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org. 
 *
 */
package org.helios.jzab.agent.net.active;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;

import javax.management.AttributeChangeNotification;
import javax.management.ListenerNotFoundException;
import javax.management.MBeanNotificationInfo;
import javax.management.Notification;
import javax.management.NotificationBroadcaster;
import javax.management.NotificationBroadcasterSupport;
import javax.management.NotificationEmitter;
import javax.management.NotificationFilter;
import javax.management.NotificationListener;
import javax.management.ObjectName;

import org.helios.jzab.agent.SystemClock;
import org.helios.jzab.agent.commands.CommandManager;
import org.helios.jzab.agent.commands.ICommandProcessor;
import org.helios.jzab.agent.internal.jmx.ScheduledThreadPoolFactory;
import org.helios.jzab.agent.internal.jmx.ThreadPoolFactory;
import org.helios.jzab.agent.internal.jmx.TrackedScheduledFuture;
import org.helios.jzab.agent.logging.LoggerManager;
import org.helios.jzab.agent.net.active.ActiveHost.ActiveHostCheck;
import org.helios.jzab.agent.net.active.collection.IResultCollector;
import org.helios.jzab.agent.net.active.schedule.PassiveScheduleBucket;
import org.helios.jzab.agent.net.routing.JSONResponseHandler;
import org.helios.jzab.agent.net.routing.RoutingObjectName;
import org.helios.jzab.agent.net.routing.RoutingObjectNameFactory;
import org.helios.jzab.util.JMXHelper;
import org.helios.jzab.util.StringHelper;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * <p>Title: ActiveHost</p>
 * <p>Description: Represents a host with a list of items that will be actively monitored by the agent.</p> 
 * <p>Company: Helios Development Group LLC</p>
 * @author Whitehead (nwhitehead AT heliosdev DOT org)
 * <p><code>org.helios.jzab.agent.net.active.ActiveHost</code></p>
 */
public class ActiveHost
        implements JSONResponseHandler, ActiveHostMXBean, Iterable<ActiveHostCheck>, NotificationBroadcaster {
    /** Instance logger */
    protected final Logger log = LoggerFactory.getLogger(getClass());
    /** The host name we're monitoring for */
    protected final String hostName;
    /** The ActiveServer parent for this host */
    protected final ActiveServer server;
    /** The scheduled task to refresh active checks */
    protected TrackedScheduledFuture refreshTask = null;
    /** The frequency in seconds that this host's marching orders should be refreshed */
    protected long refreshPeriod;
    /** The changes in the last refresh */
    protected final LastRefreshChange lastRefreshChange = new LastRefreshChange();
    /** Checks that have been removed */
    protected final Set<String> removedCheckNames = new CopyOnWriteArraySet<String>();

    /** The command manager to execute checks */
    protected final CommandManager commandManager = CommandManager.getInstance();
    /** The notification manager */
    protected final NotificationBroadcasterSupport notificationBroadcaster = new NotificationBroadcasterSupport(
            ThreadPoolFactory.getInstance("NotificationProcessor"));
    /** The notif info */
    protected final MBeanNotificationInfo[] notificationInfos = new MBeanNotificationInfo[] {
            new MBeanNotificationInfo(new String[] { "activehost.statechange" },
                    AttributeChangeNotification.class.getName(), "Fired when the state of an ActiveHost changes") };
    /** The sequence number generator for JMX notification sequences */
    protected final AtomicLong notificationSequence = new AtomicLong(0L);

    /** This host's JMX ObjectName */
    protected final ObjectName objectName;
    /** The state of this host */
    protected final AtomicReference<ActiveHostState> state = new AtomicReference<ActiveHostState>(
            ActiveHostState.INIT);
    /** The timestamp of the currently defined state in ms. */
    protected long stateTimestamp = System.currentTimeMillis();
    /** The configured active host checked keyed by item name */
    protected final Map<String, ActiveHostCheck> hostChecks = new ConcurrentHashMap<String, ActiveHostCheck>();
    /** The configured discovery active host checked keyed by item name */
    protected final Map<String, ActiveHostCheck> hostDiscoveryChecks = new ConcurrentHashMap<String, ActiveHostCheck>();

    /** The schedule bucket map for this active host */
    protected final PassiveScheduleBucket<ActiveHostCheck, ActiveHost> scheduleBucket;
    /** The routing object names for this host */
    protected final RoutingObjectName[] routingNames;

    /** The JSON key for the active check mtime */
    public static final String CHECK_MTIME = "mtime";
    /** The JSON key for the active check delay */
    public static final String CHECK_DELAY = "delay";
    /** The JSON key for the active check item key */
    public static final String CHECK_ITEM_KEY = "key";
    /** The JSON key for the active check last log size */
    public static final String CHECK_LASTLOG_SIZE = "lastlogsize";

    /**
     * Creates a new ActiveHost
     * @param server The parent ActiveServer
     * @param hostName The host name we're monitoring for
     * @param refreshPeriod The frequency in seconds that this host's marching orders should be refreshed 
     */
    public ActiveHost(ActiveServer server, String hostName, long refreshPeriod) {
        if (hostName == null || hostName.trim().isEmpty())
            throw new IllegalArgumentException("The passed host name was null or blank", new Throwable());
        this.server = server;
        this.hostName = hostName;
        this.refreshPeriod = refreshPeriod;
        scheduleBucket = new PassiveScheduleBucket<ActiveHostCheck, ActiveHost>(server.scheduleBucket, this);
        routingNames = new RoutingObjectName[] { RoutingObjectNameFactory.getInstance().getRoute(true, KEY_REQUEST,
                VALUE_ACTIVE_CHECK_REQUEST, KEY_HOST, hostName) };
        RoutingObjectNameFactory.getInstance().registerJSONResponseHandler(this);
        log.debug("Created ActiveHost [{}]", hostName);
        objectName = JMXHelper
                .objectName(new StringBuilder(server.getObjectName().toString()).append(",host=").append(hostName));
        JMXHelper.registerMBean(JMXHelper.getHeliosMBeanServer(), objectName, this);
        scheduleNextRefresh();
    }

    /**
     * Returns the scheduled task for the next refresh
     * @return the refreshTask
     */
    @Override
    public TrackedScheduledFuture getRefreshTask() {
        return refreshTask;
    }

    /**
     * Schedules the next refresh
     */
    protected void scheduleNextRefresh() {
        final ActiveHost finalHost = this;
        refreshTask = ScheduledThreadPoolFactory.getInstance("Scheduler")
                .schedule("Refresh Task for [" + getId() + "]", new Runnable() {
                    @Override
                    public void run() {
                        try {
                            ActiveAgent.getInstance().requestActiveChecks(server, finalHost, true);
                        } catch (Exception e) {
                            e.printStackTrace(System.err);
                            scheduleNextRefresh();
                        }
                    }
                }, refreshPeriod, TimeUnit.SECONDS);
    }

    /**
     * Returns a set of ActiveHostChecks for the passed delay
     * @param delay The delay to get checks for
     * @return a set of ActiveHostChecks 
     */
    public Set<ActiveHostCheck> getChecksForDelay(long delay) {
        return Collections.unmodifiableSet(scheduleBucket.get(delay));
    }

    /**
     * Returns the discovery checks for this host
     * @return A set of discovery checks 
     */
    public Set<ActiveHostCheck> getDiscoveryChecks() {
        return Collections.unmodifiableSet(new HashSet<ActiveHostCheck>(hostDiscoveryChecks.values()));
    }

    /**
     * Determines if this active host requires a marching orders refresh
     * @param currentTime The current time in ms.
     * @return true if this active host requires a marching orders refresh
     */
    public boolean requiresRefresh(long currentTime) {
        if (state.get().requiresUpdate())
            return true;
        return TimeUnit.SECONDS.convert(currentTime - stateTimestamp, TimeUnit.MILLISECONDS) > refreshPeriod;
    }

    /**
     * Returns the state of this host
     * @return the state of this host
     */
    @Override
    public String getState() {
        return state.get().name();
    }

    /**
     * Returns the effective time of the last state change in seconds
     * @return the effective time of the last state change in seconds
     */
    @Override
    public long getStateTimestamp() {
        return TimeUnit.SECONDS.convert(stateTimestamp, TimeUnit.MILLISECONDS);
    }

    /**
     * Returns the effective time of the last state change as a java date
     * @return the effective time of the last state change as a java date
     */
    @Override
    public Date getStateDate() {
        return new Date(stateTimestamp);
    }

    /**
     * Returns the number of active checks for this host
     * @return the number of active checks for this host
     */
    @Override
    public int getActiveCheckCount() {
        return hostChecks.size();
    }

    /**
     * {@inheritDoc}
     * @see org.helios.jzab.agent.net.routing.JSONResponseHandler#jsonResponse(org.helios.jzab.agent.net.routing.RoutingObjectName, org.json.JSONObject)
     */
    @Override
    public void jsonResponse(RoutingObjectName routing, JSONObject response) throws JSONException {
        log.debug("Handling JSON Response [{}]", response);
        String requestType = routing.getKeyProperty(KEY_REQUEST);
        if (VALUE_ACTIVE_CHECK_REQUEST.equals(requestType)) {
            int[] results = upsertActiveChecks(response.getJSONArray(KEY_DATA));
            log.debug("Active Host [{}] Check Update Results (adds/updates/nochanges/removeds) {}", hostName,
                    Arrays.toString(results));
        }
    }

    /**
     * {@inheritDoc}
     * @see org.helios.jzab.agent.net.routing.JSONResponseHandler#getRoutingObjectNames()
     */
    @Override
    public RoutingObjectName[] getRoutingObjectNames() {
        return routingNames;
    }

    /**
     * Returns a string displaying each schedule bucket delay and the number of checks in each bucket.
     * @return a string displaying the schedule bucket
     */
    public String displayScheduleBuckets() {
        StringBuilder sb = new StringBuilder("Schedule Bucket for [");
        sb.append(this.toString()).append("]");
        for (Map.Entry<Long, Set<ActiveHostCheck>> entry : scheduleBucket.entrySet()) {
            sb.append("\n\t").append(entry.getKey()).append(":").append(entry.getValue().size());
        }
        sb.append("\n");
        return sb.toString();
    }

    /**
     * Returns an array of the unique schedule windows for this active host's checks
     * @return an array of longs representing the unique delays for this active host's checks
     */
    @Override
    public long[] getDistinctSchedules() {
        Set<Long> setOfTimes = new HashSet<Long>(scheduleBucket.keySet());
        long[] times = new long[setOfTimes.size()];
        int cnt = 0;
        for (Long time : setOfTimes) {
            times[cnt] = time;
            cnt++;
        }
        return times;
    }

    /**
     * Executes all the checks for the passed delay window
     * @param delay The delay window
     * @param collector The collector stream to write the results to
     */
    public void executeChecks(long delay, IResultCollector collector) {
        Set<ActiveHostCheck> checks = scheduleBucket.get(delay);
        for (ActiveHostCheck check : checks) {
            try {
                check.execute(collector);
            } catch (Exception e) {
            }
        }
    }

    /**
     * Returns the logging level for this active host
     * @return the logging level for this active host
     */
    @Override
    public String getLevel() {
        return LoggerManager.getInstance().getLoggerLevelManager().getLoggerLevel(getClass().getName());
    }

    /**
     * Sets the logger level for this active host
     * @param level The level to set this logger to
     */
    @Override
    public void setLevel(String level) {
        LoggerManager.getInstance().getLoggerLevelManager().setLoggerLevel(getClass().getName(), level);
    }

    /**
     * Returns a map of the number of hosts registered for checks for each delay
     * @return a map of the number of hosts registered for checks for each delay
     */
    @Override
    public Map<Long, Integer> getScheduleCounts() {
        Map<Long, Integer> map = new HashMap<Long, Integer>(scheduleBucket.size());
        for (Map.Entry<Long, Set<ActiveHostCheck>> entry : scheduleBucket.entrySet()) {
            map.put(entry.getKey(), entry.getValue().size());
        }
        return map;
    }

    /**
     * Returns the ID of this active host
     * @return the ID of this active host
     */
    @Override
    public String getId() {
        return server.getId() + "/" + hostName;
    }

    /**
     * Updates the state of this active host, sending a JMX attribute change notification if this is really a valid state change
     * @param state The new state
     */
    protected void setState(ActiveHostState state) {
        if (state == null)
            throw new IllegalArgumentException("The passed ActiveHostState was null", new Throwable());
        ActiveHostState priorState = this.state.getAndSet(state);
        if (priorState != state) {
            this.stateTimestamp = SystemClock.currentTimeMillis();
            sendNotification(new AttributeChangeNotification(objectName, notificationSequence.incrementAndGet(),
                    this.stateTimestamp, String.format("State change from [%s] to [%s]", priorState, state),
                    "State", ActiveHostState.class.getName(), priorState.name(), state.name()));
        }
    }

    /**
     * Executes all the active checks for this host and submits them
     */
    @Override
    public void executeChecks() {
        ActiveAgent.getInstance().executeChecks(this);
    }

    /**
     * Requests an updates on active checks assigned to this host (forced)
     */
    @Override
    public void requestMarchingOrders() {
        ActiveAgent.getInstance().requestActiveChecks(getServer(), this, true);
    }

    /**
     * Executes all the checks for this host
     * @param collector The result collection stream
     */
    public void executeChecks(IResultCollector collector) {
        for (ActiveHostCheck check : hostChecks.values()) {
            collector.addResult(check.call());
        }
    }

    /**
     * Returns the changes made in the last refresh 
     * @return the changes made in the last refresh
     */
    @Override
    public LastRefreshChange getLastRefreshChange() {
        return lastRefreshChange;
    }

    /**
     * Upserts the active checks for this host
     * @param activeChecks An array of json formatted active checks
     * @return An array of ints representing the counts of: <ol>
     *    <li>The number of checks added<li>
     *  <li>The number of checks updated<li>
     *  <li>The number of checks with no change<li>
     *  <li>The number of checks deleted<li>
     * </ol>
     * 
     */
    public synchronized int[] upsertActiveChecks(JSONArray activeChecks) {
        final long start = SystemClock.currentTimeMillis();
        markAllChecks(true);
        int adds = 0, updates = 0, nochanges = 0;
        try {
            for (int i = 0; i < activeChecks.length(); i++) {
                JSONObject activeCheck = activeChecks.getJSONObject(i);
                String key = activeCheck.getString(CHECK_ITEM_KEY);
                long mtime = activeCheck.getLong(CHECK_MTIME);
                long delay = activeCheck.getLong(CHECK_DELAY);
                ActiveHostCheck ahc = hostChecks.get(key);
                if (ahc == null) {
                    // new ActiveHostCheck
                    try {
                        ahc = new ActiveHostCheck(hostName, key, delay, mtime);
                        if (ahc.isDiscovery()) {
                            hostDiscoveryChecks.put(key, ahc);
                        } else {
                            hostChecks.put(key, ahc);
                        }

                        log.trace("New ActiveHostCheck [{}]", ahc);
                        sendNotification(new Notification("host.activecheck.added", objectName,
                                notificationSequence.incrementAndGet(), this.stateTimestamp,
                                String.format("Removed Active Check [%s]", ahc.itemKey)));
                        adds++;
                    } catch (Exception e) {
                        log.error("Failed to create active host check for host/key [{}]: [{}]",
                                hostName + "/" + key, e.getMessage());
                        //log.debug("Failed to create active host check for host/key [{}]", hostName + "/" + key, e);
                    }
                } else {
                    if (ahc.update(delay, mtime)) {
                        // updated ActiveHostCheck
                        log.debug("Updated ActiveHostCheck [{}]", ahc);
                        sendNotification(new Notification("host.activecheck.updated", objectName,
                                notificationSequence.incrementAndGet(), this.stateTimestamp,
                                String.format("Removed Active Check [%s]", ahc.itemKey)));
                        updates++;
                    } else {
                        // no change ActiveHostCheck
                        nochanges++;
                    }
                    ahc.marked = false;
                }
            }
            int checksRemoved = clearMarkedChecks();
            log.info("Removed [{}] Active Host Checks", checksRemoved);
            setState(ActiveHostState.ACTIVE);
            long elapsed = SystemClock.currentTimeMillis() - start;
            lastRefreshChange.update(elapsed, checksRemoved, adds, updates, nochanges);
            return new int[] { adds, updates, nochanges, checksRemoved };
        } catch (Exception e) {
            log.error("Failed to upsert Active Host Checks [{}]", e.getMessage());
            log.debug("Failed to upsert Active Host Checks for JSON [{}]", activeChecks, e);
            return null;
        } finally {
            scheduleNextRefresh();
        }
    }

    /**
     * Sets the upsert markerson all active host checks
     * @param enabled the state to set the markers to 
     */
    protected void markAllChecks(boolean enabled) {
        for (ActiveHostCheck ac : hostChecks.values()) {
            ac.marked = enabled;
        }
    }

    /**
     * Removes all marked active host checks
     * @return The number of active host checks removed
     */
    protected int clearMarkedChecks() {
        Set<ActiveHostCheck> checksToRemove = new HashSet<ActiveHostCheck>();
        for (ActiveHostCheck ac : hostChecks.values()) {
            if (ac.marked) {
                checksToRemove.add(ac);
            }
        }
        for (ActiveHostCheck ac : checksToRemove) {
            hostChecks.remove(ac.itemKey);
            scheduleBucket.removeItem(ac.delay, ac);
            if (log.isDebugEnabled())
                removedCheckNames.add(ac.itemKey);
            sendNotification(
                    new Notification("host.activecheck.removed", objectName, notificationSequence.incrementAndGet(),
                            this.stateTimestamp, String.format("Removed Active Check [%s]", ac.itemKey)));
        }
        return checksToRemove.size();
    }

    /**
     * Determines if this active host requires a marching orders refresh
     * @return true if this active host requires a marching orders refresh
     */
    public boolean isRequiresRefresh() {
        return requiresRefresh(System.currentTimeMillis());
    }

    /**
     * Returns the refresh period in seconds.
     * @return the refresh period in seconds.
     */
    @Override
    public long getRefreshPeriod() {
        return refreshPeriod;
    }

    /**
     * Sets the refresh period in seconds
     * @param refreshPeriod the refresh period in seconds
     */
    @Override
    public void setRefreshPeriod(long refreshPeriod) {
        if (refreshPeriod < 2)
            throw new IllegalArgumentException("Refresh period must be at least 1 second", new Throwable());
        this.refreshPeriod = refreshPeriod;
    }

    /**
     * Return the host name
     * @return the hostName
     */
    @Override
    public String getHostName() {
        return hostName;
    }

    /**
     * Returns a collection of the currently active host checks
     * @return a collection of the currently active host checks
     */
    public Set<ActiveHostCheck> getHostChecks() {
        return Collections.unmodifiableSet(new HashSet<ActiveHostCheck>(hostChecks.values()));
    }

    /**
     * Returns the parent active server
     * @return the parent active server
     */
    public ActiveServer getServer() {
        return server;
    }

    /**
     * {@inheritDoc}
     * @see java.lang.Iterable#iterator()
     */
    @Override
    public Iterator<ActiveHostCheck> iterator() {
        return Collections.unmodifiableCollection(hostChecks.values()).iterator();
    }

    /**
     * Returns a set of the names of removed checks. 
     * Only populated when the logger is in DEBUG.
     * @return the removed Check Names
     */
    public Set<String> getRemovedCheckNames() {
        return Collections.unmodifiableSet(removedCheckNames);
    }

    /**
     * Clears the removed check names
     */
    public void clearRemovedCheckNames() {
        removedCheckNames.clear();
    }

    public interface ActiveHostCheckMBean {
        public String getItemKey();

        public long getDelay();

        public long getMTime();

        public String getArguments();

        public long getLastExecuteTime();

        public Date getLastExecuteDate();

        public long getLastRefreshTime();

        public Date getLastRefreshDate();

        public String call();

        public boolean isDiscovery();
    }

    /**
     * <p>Title: ActiveHostCheck</p>
     * <p>Description: Represents an active check on host item being performed on behalf of the zabbix server</p> 
     * <p>Company: Helios Development Group LLC</p>
     * @author Whitehead (nwhitehead AT heliosdev DOT org)
     * <p><code>org.helios.jzab.agent.net.active.ActiveHost.ActiveHostCheck</code></p>
     */
    public class ActiveHostCheck implements Callable<String>, ActiveHostCheckMBean {
        /** The host name the item being checked  is for */
        protected final String hostName;
        /** The command name to execute */
        protected final String commandName;
        /** The key of the item being checked */
        protected final String itemKey;
        /** The key of the item being checked escaped */
        protected final String itemKeyEsc;
        /** Indicates if this is a discovery command */
        protected final boolean discovery;

        /** The period of the check in seconds */
        protected long delay;
        /** The last mtime of the check (whatever that is)  */
        protected long mtime;
        /** The last time this check was refreshed */
        protected long lastRefreshTime;
        /** The last time this check was executed */
        protected long lastExecuteTime;

        /** The update mark, set when upsert starts, cleared on match, deletes this check if still marked on upsert end */
        protected boolean marked = false;
        /** The command processor for this check */
        protected final ICommandProcessor commandProcessor;
        /** The parsed arguments to pass to the command processor for this check */
        protected final String[] processorArguments;

        /**
         * Creates a new ActiveHostCheck
         * @param hostName The host name the item being checked  is for 
         * @param itemKey The key of the item being checked
         * @param delay The period of the check in seconds 
         * @param mtime The mtime of the active check
         */
        public ActiveHostCheck(String hostName, String itemKey, long delay, long mtime) {
            this.hostName = hostName;
            this.itemKey = itemKey;
            this.delay = delay;
            this.mtime = mtime;
            lastRefreshTime = System.currentTimeMillis();
            itemKeyEsc = StringHelper.escapeQuotes(this.itemKey);
            commandName = commandManager.parseCommandName(itemKey);
            String[] ops = commandManager.parseCommandString(itemKey);
            if (ops == null) {
                throw new RuntimeException("Command Manager Failed to parse item key [" + itemKey + "]",
                        new Throwable());
            }
            commandProcessor = commandManager.getCommandProcessor(ops[0]);
            if (commandProcessor == null) {
                throw new RuntimeException(
                        "Command Manager Failed to get command processor for name [" + ops[0] + "]",
                        new Throwable());
            }
            discovery = commandProcessor.isDiscovery();
            if (ops.length > 1) {
                processorArguments = new String[ops.length - 1];
                System.arraycopy(ops, 1, processorArguments, 0, ops.length - 1);
            } else {
                processorArguments = CommandManager.EMPTY_ARGS;
            }
            scheduleBucket.addItem(delay, this);
        }

        /** The JSON response template */
        public static final String RESPONSE_TEMPLATE = "{ \"host\": \"%s\", \"key\": \"%s\", \"value\": \"%s\", \"clock\": %s },";

        /**
         * Executes this check and returns the formated string result
         * {@inheritDoc}
         * @see java.util.concurrent.Callable#call()
         */
        @Override
        public String call() {
            Object result = commandProcessor.execute(commandName, processorArguments);
            return String.format(RESPONSE_TEMPLATE, hostName, itemKeyEsc,
                    StringHelper.escapeQuotes(result.toString()), SystemClock.currentTimeSecs());
        }

        /**
         * Executes a discovery check
         * @return the discovery check result
         */
        public Object discover() {
            return commandProcessor.execute(commandName, processorArguments);
        }

        /**
         * Indicates if this is a discovery command
         * @return true if this is a discovery command
         */
        @Override
        public boolean isDiscovery() {
            return discovery;
        }

        /**
         * Executes this check and writes the result to the passed byte buffer
         * @param collector The collector stream to write the results to
         */
        public void execute(IResultCollector collector) {
            lastExecuteTime = collector.getCollectTime();
            collector.addResult(call());
        }

        /**
         * Updates the delay and mtime of this check
         * @param delay The delay
         * @param mtime The mtime
         * @return true if either value changed, false otherwise
         */
        public boolean update(long delay, long mtime) {
            lastRefreshTime = System.currentTimeMillis();
            boolean updated = false;
            if (this.delay != delay) {
                // removes this check from it's current bucket
                scheduleBucket.removeItem(this.delay, this);
                // update this check's delay
                this.delay = delay;
                // add this check back into the new bucket
                scheduleBucket.addItem(this.delay, this);
                updated = true;
            }
            if (this.mtime != mtime) {
                this.mtime = mtime;
                updated = true;
            }
            return updated;
        }

        /**
         * Returns the period of the check in seconds
         * @return the delay
         */
        public long getDelay() {
            return delay;
        }

        /**
         * Returns the mtime of the check (whatever that is)
         * @return the mtime
         */
        public long getMTime() {
            return mtime;
        }

        /**
         * Sets the period of the check in seconds
         * @param delay the delay to set
         */
        public void setDelay(long delay) {
            this.delay = delay;
        }

        /**
         * Returns the key of the item being checked
         * @return the item Key
         */
        public String getItemKey() {
            return itemKey;
        }

        private ActiveHost getOuterType() {
            return ActiveHost.this;
        }

        /**
         * {@inheritDoc}
         * @see java.lang.Object#toString()
         */
        @Override
        public String toString() {
            return String.format("ActiveHostCheck [host=%s, itemKey=%s, delay=%s]", hostName, itemKey, delay);
        }

        /**
         * {@inheritDoc}
         * @see java.lang.Object#hashCode()
         */
        @Override
        public int hashCode() {
            final int prime = 31;
            int result = 1;
            result = prime * result + getOuterType().hashCode();
            result = prime * result + ((hostName == null) ? 0 : hostName.hashCode());
            result = prime * result + ((itemKey == null) ? 0 : itemKey.hashCode());
            return result;
        }

        /**
         * {@inheritDoc}
         * @see java.lang.Object#equals(java.lang.Object)
         */
        @Override
        public boolean equals(Object obj) {
            if (this == obj) {
                return true;
            }
            if (obj == null) {
                return false;
            }
            if (getClass() != obj.getClass()) {
                return false;
            }
            ActiveHostCheck other = (ActiveHostCheck) obj;
            if (!getOuterType().equals(other.getOuterType())) {
                return false;
            }
            if (hostName == null) {
                if (other.hostName != null) {
                    return false;
                }
            } else if (!hostName.equals(other.hostName)) {
                return false;
            }
            if (itemKey == null) {
                if (other.itemKey != null) {
                    return false;
                }
            } else if (!itemKey.equals(other.itemKey)) {
                return false;
            }
            return true;
        }

        /**
         * The last date this check was refreshed 
         * @return the last Refresh date
         */
        public Date getLastRefreshDate() {
            return new Date(lastRefreshTime);
        }

        /**
         * The last time this check was refreshed in long UTC ms.
         * @return the last Refresh Time
         */
        public long getLastRefreshTime() {
            return lastRefreshTime;
        }

        /**
         * The last time this check was executed 
         * @return the last Execute date
         */
        public Date getLastExecuteDate() {
            return new Date(lastExecuteTime);
        }

        /**
         * The last time this check was executed in long UTC ms.
         * @return the last Execute Time
         */
        public long getLastExecuteTime() {
            return lastExecuteTime;
        }

        /**
         * Returns the processor arguments flattened out to a string
         * @return the processor arguments 
         */
        public String getArguments() {
            return Arrays.toString(processorArguments);
        }

    }

    /**
     * Adds a listener.
     *
     * @param listener The listener to receive notifications.
     * @param filter The filter object. If filter is null, no
     * filtering will be performed before handling notifications.
     * @param handback An opaque object to be sent back to the
     * listener when a notification is emitted. This object cannot be
     * used by the Notification broadcaster object. It should be
     * resent unchanged with the notification to the listener.
     *
     * @exception IllegalArgumentException thrown if the listener is null.
     *
     * @see #removeNotificationListener
     */
    @Override
    public void addNotificationListener(NotificationListener listener, NotificationFilter filter, Object handback) {
        notificationBroadcaster.addNotificationListener(listener, filter, handback);
    }

    /**
     * Returns the notification info for this ActiveHost
     * @return the notification info for this ActiveHost
     * @see javax.management.NotificationBroadcasterSupport#getNotificationInfo()
     */
    @Override
    public MBeanNotificationInfo[] getNotificationInfo() {
        return notificationBroadcaster.getNotificationInfo();
    }

    /**
     * Removes a listener from this MBean.  If the listener
     * has been registered with different handback objects or
     * notification filters, all entries corresponding to the listener
     * will be removed.
     *
     * @param listener A listener that was previously added to this MBean.
     *
     * @exception ListenerNotFoundException The listener is not
     * registered with the MBean.
     *
     * @see #addNotificationListener
     * @see NotificationEmitter#removeNotificationListener
     */
    /**
     * @param listener A listener that was previously added to this MBean.
     * @param filter The filter that the listener was registered with
     * @param handback The handback that the listener was registered with
     * @throws ListenerNotFoundException The listener is not registered with the MBean.
     */
    public void removeNotificationListener(NotificationListener listener, NotificationFilter filter,
            Object handback) throws ListenerNotFoundException {
        notificationBroadcaster.removeNotificationListener(listener, filter, handback);
    }

    /**
     * Removes a listener from this MBean.  If the listener
     * has been registered with different handback objects or
     * notification filters, all entries corresponding to the listener
     * will be removed.
     *
     * @param listener A listener that was previously added to this
     * MBean.
     *
     * @exception ListenerNotFoundException The listener is not registered with the MBean.
     *
     * @see #addNotificationListener
     * @see NotificationEmitter#removeNotificationListener
     */
    @Override
    public void removeNotificationListener(NotificationListener listener) throws ListenerNotFoundException {
        notificationBroadcaster.removeNotificationListener(listener);
    }

    /**
     * Sends a JMX notification
     * @param notif The notification to send
     * @see javax.management.NotificationBroadcasterSupport#sendNotification(javax.management.Notification)
     */
    public void sendNotification(Notification notif) {
        notificationBroadcaster.sendNotification(notif);
    }

    /**
     * {@inheritDoc}
     * @see java.lang.Object#toString()
     */
    @Override
    public String toString() {
        StringBuilder builder = new StringBuilder();
        builder.append("ActiveHost [");
        if (hostName != null) {
            builder.append("hostName=");
            builder.append(hostName);
            builder.append(", ");
        }
        if (server != null) {
            builder.append("server=");
            builder.append(server);
        }
        builder.append("]");
        return builder.toString();
    }

    public static interface LastRefreshChangeMBean {
        public int getRemoved();

        public int getAdded();

        public int getUpdated();

        public int getNoChange();

        public long getElapsedTime();

        public Date getDate();
    }

    public static class LastRefreshChange implements LastRefreshChangeMBean {
        private int removed = 0;
        private int added = 0;
        private int updated = 0;
        private int noChange = 0;
        private long elapsed = 0L;
        private long time = 0L;

        /**
         * Updates in this order: removed, added, updated, noChange
         * @param values
         */
        public void update(long elapsed, int... values) {
            removed = values[0];
            added = values[1];
            updated = values[2];
            noChange = values[3];
            time = SystemClock.currentTimeMillis();
            this.elapsed = elapsed;
        }

        public long getElapsedTime() {
            return elapsed;
        }

        public Date getDate() {
            return new Date(time);
        }

        /**
         * Returns 
         * @return the removed
         */
        @Override
        public int getRemoved() {
            return removed;
        }

        /**
         * Returns 
         * @return the added
         */
        @Override
        public int getAdded() {
            return added;
        }

        /**
         * Returns 
         * @return the updated
         */
        @Override
        public int getUpdated() {
            return updated;
        }

        /**
         * Returns 
         * @return the noChange
         */
        @Override
        public int getNoChange() {
            return noChange;
        }

    }

}