jdk/test/javax/management/namespace/NamespaceController.java
author dfuchs
Thu, 04 Sep 2008 14:46:36 +0200
changeset 1156 bbc2d15aaf7a
permissions -rw-r--r--
5072476: RFE: support cascaded (federated) MBean Servers 6299231: Add support for named MBean Servers Summary: New javax.management.namespace package. Reviewed-by: emcmanus

/*
 * Copyright 2008 Sun Microsystems, Inc.  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.  Sun designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
 * CA 95054 USA or visit www.sun.com if you need additional information or
 * have any questions.
 */

import com.sun.jmx.namespace.ObjectNameRouter;
import static javax.management.namespace.JMXNamespaces.NAMESPACE_SEPARATOR;

import java.io.IOException;
import java.util.Arrays;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.management.InstanceAlreadyExistsException;
import javax.management.InstanceNotFoundException;
import javax.management.JMX;
import javax.management.ListenerNotFoundException;
import javax.management.MBeanException;
import javax.management.MBeanNotificationInfo;
import javax.management.MBeanRegistration;
import javax.management.MBeanRegistrationException;
import javax.management.MBeanServer;
import javax.management.MBeanServerConnection;
import javax.management.MalformedObjectNameException;
import javax.management.NotCompliantMBeanException;
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;
import javax.management.ReflectionException;
import javax.management.namespace.JMXNamespace;
import javax.management.namespace.JMXNamespaces;
import javax.management.namespace.JMXRemoteNamespaceMBean;
import javax.management.remote.JMXServiceURL;

/**
 * The {@code NamespaceController} MBean makes it possible to easily
 * create mount points ({@linkplain JMXNamespace JMXNamespaces}) in an
 * {@code MBeanServer}.
 * There is at most one instance of NamespaceController in an
 * MBeanServer - which can be created using the {@link #createInstance
 * createInstance} method. The {@code NamespaceController} MBean will
 * make it possible to remotely create name spaces by mounting remote
 * MBeanServers into the MBeanServer in which it was registered.
 */
// This API was originally in the draft of javax/management/namespaces
// but we decided to retire it. Rather than removing all the associated
// tests I have moved the API to the test hierarchy - so it is now used as
// an additional (though somewhat complex) test case...
//
public class NamespaceController implements NamespaceControllerMBean,
        NotificationEmitter, MBeanRegistration {

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

    private static long seqNumber=0;

    private final NotificationBroadcasterSupport broadcaster =
            new NotificationBroadcasterSupport();

    private volatile MBeanServer mbeanServer = null;

    private volatile ObjectName objectName = null;

    //was: NamespaceController.class.getPackage().getName()
    public static final String NAMESPACE_CONTROLLER_DOMAIN = "jmx.ns";

    /**
     * Creates a new NamespaceController.
     * Using {@link #createInstance} should be preferred.
     **/
    public NamespaceController() {
        this(null);
    }

    public NamespaceController(MBeanServer mbeanServer) {
        this.mbeanServer = mbeanServer;
    }

    /*
     * MBeanNotification support
     * You shouldn't update these methods
     */
    public final void addNotificationListener(NotificationListener listener,
            NotificationFilter filter, Object handback) {
        broadcaster.addNotificationListener(listener, filter, handback);
    }

    public MBeanNotificationInfo[] getNotificationInfo() {
        return new MBeanNotificationInfo[] {
        };
    }

    public final void removeNotificationListener(NotificationListener listener)
        throws ListenerNotFoundException {
        broadcaster.removeNotificationListener(listener);
    }

    public final void removeNotificationListener(NotificationListener listener,
            NotificationFilter filter, Object handback)
            throws ListenerNotFoundException {
        broadcaster.removeNotificationListener(listener, filter, handback);
    }

    public static synchronized long getNextSeqNumber() {
        return seqNumber++;
    }

    protected final void sendNotification(Notification n) {
        if (n.getSequenceNumber()<=0)
            n.setSequenceNumber(getNextSeqNumber());
        if (n.getSource()==null)
            n.setSource(objectName);
        broadcaster.sendNotification(n);
    }

    /**
     * The ObjectName with which this MBean was registered.
     * <p>Unless changed by subclasses, this is
     * {@code
     *  "javax.management.namespace:type="+this.getClass().getSimpleName()}.
     * @return this MBean's ObjectName, or null if this MBean was never
     *         registered.
     **/
    public final ObjectName getObjectName() {
        return objectName;
    }

    /**
     * The MBeanServer  served by this NamespaceController.
     * @return the MBeanServer  served by this NamespaceController.
     **/
    public final MBeanServer getMBeanServer() {
        return mbeanServer;
    }

    /**
     * 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. Subclasses which override {@code preRegister}
     * must call {@code super.preRegister(name,server)};
     * @param server The MBean server in which the MBean will be registered.
     * @param name The object name of the MBean.
     *        The name must be either {@code null} - or equal to that
     *        described by {@link #getObjectName}.
     * @return The name under which the MBean is to be registered.
     *         This will be the name described by {@link #getObjectName}.
     * @throws MalformedObjectNameException if the supplied name does not
     *        meet expected requirements.
     */
    public ObjectName preRegister(MBeanServer server, ObjectName name)
        throws MalformedObjectNameException {
        objectName = name;
        final ObjectName single =
                ObjectName.getInstance(NAMESPACE_CONTROLLER_DOMAIN+
                ":type="+this.getClass().getSimpleName());
        if (name!=null && !single.equals(name))
            throw new MalformedObjectNameException(name.toString());
        if (mbeanServer == null) mbeanServer = server;
        return single;
    }

    /**
     * Allows the MBean to perform any operations needed after having
     * been registered in the MBean server or after the registration has
     * failed.
     * @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) {
        //TODO postRegister implementation;
    }

    /**
     * Allows the MBean to perform any operations it needs before being
     * unregistered by the MBean server.
     * @throws Exception This exception will be caught by the MBean server and
     * re-thrown as an MBeanRegistrationException.
     */
    public void preDeregister() throws Exception {
        //TODO preDeregister implementation;
    }

    /**
     * Allows the MBean to perform any operations needed after having been
     * unregistered in the MBean server.
     */
    public void postDeregister() {
        //TODO postDeregister implementation;
    }

    public String mount(JMXServiceURL url,
            String targetPath,
            Map<String,Object> optionsMap)
            throws IOException {
        return mount(url, targetPath, "", optionsMap);
    }

    // see NamespaceControllerMBean
    public String mount(JMXServiceURL url,
            String targetPath,
            String sourcePath,
            Map<String,Object> optionsMap)
            throws IOException {

        // TODO: handle description.
        final String dirName =
                JMXNamespaces.normalizeNamespaceName(targetPath);

         try {
            final ObjectInstance moi =
                    JMXRemoteTargetNamespace.createNamespace(mbeanServer,
                    dirName,url,optionsMap,
                    JMXNamespaces.normalizeNamespaceName(sourcePath)
                    );
            final ObjectName nsMBean = moi.getObjectName();
            try {
                mbeanServer.invoke(nsMBean, "connect", null,null);
            } catch (Throwable t) {
                mbeanServer.unregisterMBean(nsMBean);
                throw t;
            }
            return getMountPointID(nsMBean);
        } catch (InstanceAlreadyExistsException x) {
            throw new IllegalArgumentException(targetPath,x);
         } catch (IOException x) {
            throw x;
        } catch (Throwable x) {
            if (x instanceof Error) throw (Error)x;
            Throwable cause = x.getCause();
            if (cause instanceof IOException)
                throw ((IOException)cause);
            if (cause == null) cause = x;

            final IOException io =
                    new IOException("connect failed: "+cause);
            io.initCause(cause);
            throw io;
        }
    }

    private String getMountPointID(ObjectName dirName) {
            return dirName.toString();
    }

    private ObjectName getHandlerName(String mountPointID) {
        try {
            final ObjectName tryit = ObjectName.getInstance(mountPointID);
            final ObjectName formatted =
                    JMXNamespaces.getNamespaceObjectName(tryit.getDomain());
            if (!formatted.equals(tryit))
                throw new IllegalArgumentException(mountPointID+
                        ": invalid mountPointID");
            return formatted;
        } catch (MalformedObjectNameException x) {
            throw new IllegalArgumentException(mountPointID,x);
        }
    }

    public boolean unmount(String mountPointID)
        throws IOException {
        final ObjectName dirName = getHandlerName(mountPointID);
        if (!mbeanServer.isRegistered(dirName))
            throw new IllegalArgumentException(mountPointID+
                    ": no such name space");
        final JMXRemoteNamespaceMBean mbean =
                JMX.newMBeanProxy(mbeanServer,dirName,
                    JMXRemoteNamespaceMBean.class);
        try {
            mbean.close();
        } catch (IOException io) {
            LOG.fine("Failed to close properly - ignoring exception: "+io);
            LOG.log(Level.FINEST,
                    "Failed to close properly - ignoring exception",io);
        } finally {
            try {
                mbeanServer.unregisterMBean(dirName);
            } catch (InstanceNotFoundException x) {
                throw new IllegalArgumentException(mountPointID+
                        ": no such name space", x);
            } catch (MBeanRegistrationException x) {
                final IOException io =
                        new IOException(mountPointID +": failed to unmount");
                io.initCause(x);
                throw io;
            }
        }
        return true;
    }

    public boolean ismounted(String targetPath) {
        return mbeanServer.isRegistered(JMXNamespaces.getNamespaceObjectName(targetPath));
    }

    public ObjectName getHandlerNameFor(String targetPath) {
        return JMXNamespaces.getNamespaceObjectName(targetPath);
    }

    public String[] findNamespaces() {
        return findNamespaces(null,null,0);
    }


    private ObjectName getDirPattern(String from) {
        try {
            if (from == null)
                return ObjectName.getInstance(ALL_NAMESPACES);
            final String namespace =
                  ObjectNameRouter.normalizeNamespacePath(from,false,true,false);
            if (namespace.equals(""))
                return ObjectName.getInstance(ALL_NAMESPACES);
            if (JMXNamespaces.getNamespaceObjectName(namespace).isDomainPattern())
                throw new IllegalArgumentException(from);
            return ObjectName.getInstance(namespace+NAMESPACE_SEPARATOR+ALL_NAMESPACES);
        } catch (MalformedObjectNameException x) {
            throw new IllegalArgumentException(from,x);
        }
    }

    public String[] findNamespaces(String from, String regex, int depth) {
        if (depth < 0) return new String[0];
        final Set<String> res = new TreeSet<String>();
        final ObjectName all = getDirPattern(from);
        Set<ObjectName> names = mbeanServer.queryNames(all,null);
        for (ObjectName dirName : names) {
            final String dir = dirName.getDomain();
            if (regex == null || dir.matches(regex))
                res.add(dir);
            if (depth > 0)
                res.addAll(Arrays.asList(findNamespaces(dir,regex,depth-1)));
        }
        return res.toArray(new String[res.size()]);
    }

    /**
     * Creates a {@link NamespaceController} MBean in the provided
     * {@link MBeanServerConnection}.
     * <p>The name of the MBean is that returned by {@link #preRegister}
     * as described by {@link #getObjectName}.
     * @throws IOException if an {@code IOException} is raised when invoking
     *         the provided connection.
     * @throws InstanceAlreadyExistsException if an MBean was already
     *         registered with the NamespaceController's name.
     * @throws MBeanRegistrationException if thrown by {@link
     * MBeanServerConnection#createMBean(java.lang.String,javax.management.ObjectName)
     * server.createMBean}
     * @throws MBeanException if thrown by {@link
     * MBeanServerConnection#createMBean(java.lang.String,javax.management.ObjectName)
     * server.createMBean}
     * @return the {@link ObjectInstance}, as returned by {@link
     * MBeanServerConnection#createMBean(java.lang.String,javax.management.ObjectName)
     * server.createMBean}
     **/
    public static ObjectInstance createInstance(MBeanServerConnection server)
        throws IOException, InstanceAlreadyExistsException,
            MBeanRegistrationException, MBeanException {
        try {
            final ObjectInstance instance =
                server.createMBean(NamespaceController.class.getName(), null);
            return instance;
        } catch (NotCompliantMBeanException ex) {
            throw new RuntimeException("unexpected exception: " + ex, ex);
        } catch (ReflectionException ex) {
            throw new RuntimeException("unexpected exception: " + ex, ex);
        }
    }

    private final static String ALL_NAMESPACES=
            "*"+NAMESPACE_SEPARATOR+":"+
            JMXNamespace.TYPE_ASSIGNMENT;

}