public abstract class

MonitoredHost

extends Object
/*
 * Copyright (c) 2004, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code 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 General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package sun.jvmstat.monitor;

import java.util.*;
import java.net.*;
import java.lang.reflect.*;

import sun.jvmstat.monitor.event.HostListener;

/**
 * An abstraction for a host that contains instrumented Java Virtual
 * Machines. The class provides abstract factory methods for creating
 * concrete instances of this class and factory methods for creating
 * {@link MonitoredVm} instances. Concrete implementations of this class
 * provide methods for managing the communications protocols and provide
 * for event notification.
 *
 * @author Brian Doherty
 * @since 1.5
 *
 * @see HostIdentifier
 * @see VmIdentifier
 * @see MonitoredVm
 * @see HostListener
 */
public abstract class MonitoredHost {
    private static Map<HostIdentifier, MonitoredHost> monitoredHosts =
                new HashMap<HostIdentifier, MonitoredHost>();

    /*
     * The monitoring implementation override mechanism. The value of
     * this property is used as the class name for the concrete MonitoredHost
     * subclass that implements the monitoring APIs.  Setting this property
     * will cause the remaining override mechanisms to be ignored. When
     * this mechanism is used, the HostIdentifier scheme name, which
     * indicates the communications protocol, is not used to locate a
     * the protocol specific package. However, the HostIdentifier is
     * still passed to the corresponding single arg constructor.
     * This property is not expected to be set in normal circumstances.
     */
    private static final String IMPL_OVERRIDE_PROP_NAME =
            "sun.jvmstat.monitor.MonitoredHost";

    /*
     * The monitoring package name override mechanism. The value
     * the this property is used as base package name for the
     * monitoring implementation package. This property is not
     * expected to be set under normal circumstances.
     */
    private static final String IMPL_PKG_PROP_NAME =
            "sun.jvmstat.monitor.package";
    private static final String IMPL_PACKAGE =
            System.getProperty(IMPL_PKG_PROP_NAME, "sun.jvmstat.perfdata");

    /*
     * The default optimized local protocol override mechanism. The value
     * of this property is used to construct the default package name
     * for the default optimized local protocol as follows:
     *        <IMPL_PACKAGE>.monitor.<LOCAL_PROTOCOL>
     * This property is not expected to be set under normal circumstances.
     */
    private static final String LOCAL_PROTOCOL_PROP_NAME =
            "sun.jvmstat.monitor.local";
    private static final String LOCAL_PROTOCOL =
            System.getProperty(LOCAL_PROTOCOL_PROP_NAME, "local");

    /*
     * The default remote protocol override mechanism. The value of
     * this property is used to construct the default package name
     * for the default remote protocol protocol as follows:
     *        <IMPL_PACKAGE>.monitor.protocol.<REMOTE_PROTOCOL>
     * This property is not expected to be set under normal circumstances.
     */
    private static final String REMOTE_PROTOCOL_PROP_NAME =
            "sun.jvmstat.monitor.remote";
    private static final String REMOTE_PROTOCOL =
            System.getProperty(REMOTE_PROTOCOL_PROP_NAME, "rmi");

    /*
     * The default class name of the MonitoredHost implementation subclass.
     * There is no override mechanism for this variable, other than the
     * IMPL_OVERRIDE_PROP_NAME override, which is larger in scope. A concrete
     * instance of this class is expected to be found in:
     *     <IMPL_PACKAGE>.monitor.protocol.<protocol>.<MONITORED_HOST_CLASS>
     */
    private static final String MONITORED_HOST_CLASS = "MonitoredHostProvider";

    /**
     * The HostIdentifier for this MonitoredHost instance.
     */
    protected HostIdentifier hostId;

    /**
     * The polling interval, in milliseconds, for this MonitoredHost instance.
     */
    protected int interval;

    /**
     * The last Exception encountered while polling this MonitoredHost.
     */
    protected Exception lastException;

    /**
     * Factory method to construct MonitoredHost instances to manage
     * connections to the host indicated by <tt>hostIdString</tt>
     *
     * @param hostIdString a String representation of a {@link HostIdentifier}
     * @return MonitoredHost - the MonitoredHost instance for communicating
     *                         with the indicated host using the protocol
     *                         specified in hostIdString.
     * @throws MonitorException  Thrown if monitoring errors occur.
     * @throws URISyntaxException Thrown when the hostIdString is poorly
     *                            formed. This exception may get encapsulated
     *                            into MonitorException in a future revision.
     */
    public static MonitoredHost getMonitoredHost(String hostIdString)
                  throws MonitorException, URISyntaxException {
        HostIdentifier hostId = new HostIdentifier(hostIdString);
        return getMonitoredHost(hostId);
    }

    /**
     * Factory method to construct a MonitoredHost instance to manage the
     * connection to the Java Virtual Machine indicated by <tt>vmid</tt>.
     *
     * This method provide a convenient short cut for attaching to a specific
     * instrumented Java Virtual Machine. The information in the VmIdentifier
     * is used to construct a corresponding HostIdentifier, which in turn is
     * used to create the MonitoredHost instance.
     *
     * @param vmid The identifier for the target Java Virtual Machine.
     * @return MonitoredHost - The MonitoredHost object needed to attach to
     *                         the target Java Virtual Machine.
     *
     * @throws MonitorException Thrown if monitoring errors occur.
     */
    public static MonitoredHost getMonitoredHost(VmIdentifier vmid)
                 throws MonitorException {
        // use the VmIdentifier to construct the corresponding HostIdentifier
        HostIdentifier hostId = new HostIdentifier(vmid);
        return getMonitoredHost(hostId);
    }

    /**
     * Factory method to construct a MonitoredHost instance to manage the
     * connection to the host indicated by <tt>hostId</tt>.
     *
     * @param hostId the identifier for the target host.
     * @return MonitoredHost - The MonitoredHost object needed to attach to
     *                         the target host.
     *
     * @throws MonitorException Thrown if monitoring errors occur.
     */
    public static MonitoredHost getMonitoredHost(HostIdentifier hostId)
                  throws MonitorException {
        /*
         * determine the class name to load. If the system property is set,
         * use the indicated class. otherwise, use the default class.
         */
        String classname = System.getProperty(IMPL_OVERRIDE_PROP_NAME);
        MonitoredHost mh = null;

        synchronized(monitoredHosts) {
            mh = monitoredHosts.get(hostId);
            if (mh != null) {
                if (mh.isErrored()) {
                    monitoredHosts.remove(hostId);
                } else {
                    return mh;
                }
            }
        }

        hostId = resolveHostId(hostId);

        if (classname == null) {
            // construct the class name
            classname = IMPL_PACKAGE + ".monitor.protocol."
                        + hostId.getScheme() + "." + MONITORED_HOST_CLASS;
        }

        try {
            // run the constructor taking a single String parameter.
            Class<?> c = Class.forName(classname);

            Constructor cons = c.getConstructor(
                new Class[] { hostId.getClass() }
            );

            mh = (MonitoredHost)cons.newInstance(new Object[] { hostId } );

            synchronized(monitoredHosts) {
                monitoredHosts.put(mh.hostId, mh);
            }
            return mh;
        } catch (ClassNotFoundException e) {
            // from Class.forName();
            throw new IllegalArgumentException("Could not find " + classname
                                               + ": " + e.getMessage(), e);
        } catch (NoSuchMethodException e) {
            // from Class.getConstructor();
            throw new IllegalArgumentException(
                "Expected constructor missing in " + classname + ": "
                + e.getMessage(), e);
        } catch (IllegalAccessException e) {
            // from Constructor.newInstance()
            throw new IllegalArgumentException(
                "Unexpected constructor access in " + classname + ": "
                + e.getMessage(), e);
        } catch (InstantiationException e) {
            throw new IllegalArgumentException(classname + "is abstract: "
                                               + e.getMessage(), e);
        } catch (InvocationTargetException e) {
            Throwable cause = e.getCause();
            if (cause instanceof MonitorException) {
                throw (MonitorException)cause;
            }
            throw new RuntimeException("Unexpected exception", e);
        }
    }

    /**
     * Method to resolve unspecified components of the given HostIdentifier
     * by constructing a new HostIdentifier that replaces the unspecified
     * components with the default values.
     *
     * @param hostId the unresolved HostIdentifier.
     * @return HostIdentifier - a resolved HostIdentifier.
     *
     * @throws MonitorException Thrown if monitoring errors occur.
     */
    protected static HostIdentifier resolveHostId(HostIdentifier hostId)
                     throws MonitorException {
        String hostname = hostId.getHost();
        String scheme = hostId.getScheme();
        StringBuffer sb = new StringBuffer();

        assert hostname != null;

        if (scheme == null) {
            if (hostname.compareTo("localhost") == 0) {
                scheme = LOCAL_PROTOCOL;
            } else {
                scheme = REMOTE_PROTOCOL;
            }
        }

        sb.append(scheme).append(":").append(hostId.getSchemeSpecificPart());

        String frag = hostId.getFragment();
        if (frag != null) {
            sb.append("#").append(frag);
        }

        try {
            return new HostIdentifier(sb.toString());
        } catch (URISyntaxException e) {
            // programming error - HostIdentifier was valid.
            assert false;
            throw new IllegalArgumentException("Malformed URI created: "
                                               + sb.toString());
        }
    }

    /**
     * Return the resolved HostIdentifier for this MonitoredHost.
     *
     * @return HostIdentifier - the resolved HostIdentifier.
     */
    public HostIdentifier getHostIdentifier() {
        return hostId;
    }

    /* ---- Methods to support polled MonitoredHost Implementations ----- */

    /**
     * Set the polling interval for this MonitoredHost.
     *
     * @param interval the polling interval, in milliseconds
     */
    public void setInterval(int interval) {
        this.interval = interval;
    }

    /**
     * Get the polling interval.
     *
     * @return int - the polling interval in milliseconds for this MonitoredHost
     */
    public int getInterval() {
        return interval;
    }

    /**
     * Set the last exception encountered while polling this MonitoredHost.
     *
     * @param lastException the last exception encountered;
     */
    public void setLastException(Exception lastException) {
        this.lastException = lastException;
    }

    /**
     * Get the last exception encountered while polling this MonitoredHost.
     *
     * @return Exception - the last exception occurred while polling this
     *                     MonitoredHost, or <tt>null</tt> if no exception
     *                     has occurred or the exception has been cleared,
     */
    public Exception getLastException() {
        return lastException;
    }

    /**
     * Clear the last exception.
     */
    public void clearLastException() {
        lastException = null;
    }

    /**
     * Test if this MonitoredHost is in the errored state. If this method
     * returns true, then the Exception returned by getLastException()
     * indicates the Exception that caused the error condition.
     *
     * @return boolean - true if the MonitoredHost instance has experienced
     *                   an error, or false if it hasn't or if any past
     *                   error has been cleared.
     */
    public boolean isErrored() {
        return lastException != null;
    }

    /**
     * Get the MonitoredVm for the given Java Virtual Machine. The default
     * sampling interval is used for the MonitoredVm instance.
     *
     * @param id the VmIdentifier specifying the target Java Virtual Machine.
     * @return MonitoredVm - the MonitoredVm instance for the target Java
     *                       Virtual Machine.
     * @throws MonitorException Thrown if monitoring errors occur.
     */
    public abstract MonitoredVm getMonitoredVm(VmIdentifier id)
                                throws MonitorException;

    /**
     * Get the MonitoredVm for the given Java Virtual Machine. The sampling
     * interval is set to the given interval.
     *
     * @param id the VmIdentifier specifying the target Java Virtual Machine.
     * @param interval the sampling interval for the target Java Virtual Machine.
     * @return MonitoredVm - the MonitoredVm instance for the target Java
     *                       Virtual Machine.
     * @throws MonitorException Thrown if monitoring errors occur.
     */
    public abstract MonitoredVm getMonitoredVm(VmIdentifier id, int interval)
                                throws MonitorException;

    /**
     * Detach from the indicated MonitoredVm.
     *
     * @param vm the monitored Java Virtual Machine.
     * @throws MonitorException Thrown if monitoring errors occur.
     */
    public abstract void detach(MonitoredVm vm) throws MonitorException;

    /**
     * Add a HostListener. The given listener is added to the list
     * of HostListener objects to be notified of MonitoredHost related events.
     *
     * @param listener the HostListener to add.
     * @throws MonitorException Thrown if monitoring errors occur.
     */
    public abstract void addHostListener(HostListener listener)
                         throws MonitorException;

    /**
     * Remove a HostListener. The given listener is removed from the list
     * of HostListener objects to be notified of MonitoredHost related events.
     *
     * @param listener the HostListener to add.
     * @throws MonitorException Thrown if monitoring errors occur.
     */
    public abstract void removeHostListener(HostListener listener)
                         throws MonitorException;

    /**
     * Return the current set of active Java Virtual Machines for this
     * MonitoredHost. The returned Set contains {@link Integer} instances
     * holding the local virtual machine identifier, or <em>lvmid</em>
     * for each instrumented Java Virtual Machine currently available.
     *
     * @return Set - the current set of active Java Virtual Machines associated
     *               with this MonitoredHost, or the empty set of none.
     * @throws MonitorException Thrown if monitoring errors occur.
     */
    public abstract Set<Integer> activeVms() throws MonitorException;
}