jdk/src/share/classes/com/sun/jmx/interceptor/DomainDispatchInterceptor.java
author dfuchs
Thu, 04 Sep 2008 14:46:36 +0200
changeset 1156 bbc2d15aaf7a
child 1222 78e3d021d528
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.
 */

package com.sun.jmx.interceptor;

import com.sun.jmx.defaults.JmxProperties;
import com.sun.jmx.mbeanserver.MBeanInstantiator;
import com.sun.jmx.mbeanserver.Repository;
import com.sun.jmx.mbeanserver.Util;
import com.sun.jmx.namespace.DomainInterceptor;
import java.util.Queue;
import java.util.Set;

import java.util.logging.Level;
import java.util.logging.Logger;

import javax.management.MBeanServer;
import javax.management.MBeanServerDelegate;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;
import javax.management.QueryExp;
import javax.management.namespace.JMXDomain;
import static javax.management.namespace.JMXNamespaces.NAMESPACE_SEPARATOR;

/**
 * A dispatcher that dispatch incoming MBeanServer requests to
 * DomainInterceptors.
 * <p><b>
 * This API is a Sun internal API and is subject to changes without notice.
 * </b></p>
 * @since 1.7
 */
//
// See comments in  DispatchInterceptor.
//
class DomainDispatchInterceptor
        extends DispatchInterceptor<DomainInterceptor, JMXDomain> {

    /**
     * A logger for this class.
     **/
    private static final Logger LOG = JmxProperties.NAMESPACE_LOGGER;

    private static final ObjectName ALL_DOMAINS =
            JMXDomain.getDomainObjectName("*");


    /**
     *  A QueryInterceptor that perform & aggregates queries spanning several
     *  domains.
     */
    final static class AggregatingQueryInterceptor extends QueryInterceptor {

        private final DomainDispatchInterceptor parent;
        AggregatingQueryInterceptor(DomainDispatchInterceptor dispatcher) {
            super(dispatcher.localNamespace);
            parent = dispatcher;
        }

        /**
         * Perform queryNames or queryMBeans, depending on which QueryInvoker
         * is passed as argument. This is closures without closures.
         **/
        @Override
        <T> Set<T> query(ObjectName pattern, QueryExp query,
                QueryInvoker<T> invoker, MBeanServer localNamespace) {
            final Set<T> local = invoker.query(localNamespace, pattern, query);

            // Add all matching MBeans from local namespace.
            final Set<T> res = Util.cloneSet(local);

            final boolean all = (pattern == null ||
                    pattern.getDomain().equals("*"));
            if (pattern == null) pattern = ObjectName.WILDCARD;

            final String domain = pattern.getDomain();

            // If there's no domain pattern, just include the pattern's domain.
            // Otherwiae, loop over all virtual domains (parent.getKeys()).
            final String[] keys =
                (pattern.isDomainPattern() ?
                    parent.getKeys() : new String[]{domain});

            // Add all matching MBeans from each virtual domain
            //
            for (String key : keys) {
                // Only invoke those virtual domain which are selected
                // by the domain pattern
                //
                if (!all && !Util.isDomainSelected(key, domain))
                    continue;

                try {
                    final MBeanServer mbs = parent.getInterceptor(key);

                    // mbs can be null if the interceptor was removed
                    // concurrently...
                    // See handlerMap and getKeys() in DispatchInterceptor
                    //
                    if (mbs == null) continue;

                    // If the domain is selected, we can replace the pattern
                    // by the actual domain. This is safer if we want to avoid
                    // a domain (which could be backed up by an MBeanServer) to
                    // return names from outside the domain.
                    // So instead of asking the domain handler for "foo" to
                    // return all names which match "?o*:type=Bla,*" we're
                    // going to ask it to return all names which match
                    // "foo:type=Bla,*"
                    //
                    final ObjectName subPattern = pattern.withDomain(key);
                    res.addAll(invoker.query(mbs, subPattern, query));
                } catch (Exception x) {
                    LOG.finest("Ignoring exception " +
                            "when attempting to query namespace "+key+": "+x);
                    continue;
                }
            }
            return res;
        }
    }

    private final DefaultMBeanServerInterceptor localNamespace;
    private final String mbeanServerName;
    private final MBeanServerDelegate delegate;

    /**
     * Creates a DomainDispatchInterceptor with the specified
     * repository instance.
     *
     * @param outer A pointer to the MBeanServer object that must be
     *        passed to the MBeans when invoking their
     *        {@link javax.management.MBeanRegistration} interface.
     * @param delegate A pointer to the MBeanServerDelegate associated
     *        with the new MBeanServer. The new MBeanServer must register
     *        this MBean in its MBean repository.
     * @param instantiator The MBeanInstantiator that will be used to
     *        instantiate MBeans and take care of class loading issues.
     * @param repository The repository to use for this MBeanServer
     */
    public DomainDispatchInterceptor(MBeanServer         outer,
                            MBeanServerDelegate delegate,
                            MBeanInstantiator   instantiator,
                            Repository          repository,
                            NamespaceDispatchInterceptor namespaces)  {
           localNamespace = new DefaultMBeanServerInterceptor(outer,
                   delegate, instantiator,repository,namespaces);
           mbeanServerName = Util.getMBeanServerSecurityName(delegate);
           this.delegate = delegate;
    }

    final boolean isLocalHandlerNameFor(String domain,
            ObjectName handlerName) {
        if (domain == null) return true;
        return handlerName.getDomain().equals(domain) &&
               JMXDomain.TYPE_ASSIGNMENT.equals(
               handlerName.getKeyPropertyListString());
    }

    @Override
    void validateHandlerNameFor(String key, ObjectName name) {
        super.validateHandlerNameFor(key,name);
        final String[] domains = localNamespace.getDomains();
        for (int i=0;i<domains.length;i++) {
            if (domains[i].equals(key))
                throw new IllegalArgumentException("domain "+key+
                        " is not empty");
        }
    }

    @Override
    final MBeanServer getInterceptorOrNullFor(ObjectName name) {
        if (name == null) return localNamespace;
        final String domain = name.getDomain();
        if (domain.endsWith(NAMESPACE_SEPARATOR)) return localNamespace;
        if (domain.contains(NAMESPACE_SEPARATOR)) return null;
        final String localDomain = domain;
        if (isLocalHandlerNameFor(localDomain,name)) {
            LOG.finer("dispatching to local namespace");
            return localNamespace;
        }
        final DomainInterceptor ns = getInterceptor(localDomain);
        if (ns == null) {
            if (LOG.isLoggable(Level.FINER)) {
                LOG.finer("dispatching to local namespace: " + localDomain);
            }
            return getNextInterceptor();
        }
        if (LOG.isLoggable(Level.FINER)) {
            LOG.finer("dispatching to domain: " + localDomain);
        }
        return ns;
    }

    private boolean multipleQuery(ObjectName pattern) {
        if (pattern == null) return true;
        if (pattern.isDomainPattern()) return true;

        try {
            // This is a bit of a hack. If there's any chance that a JMXDomain
            // MBean name is selected by the given pattern then we must include
            // the local namespace in our search.
            // Returning true will have this effect.
            if (pattern.apply(ALL_DOMAINS.withDomain(pattern.getDomain())))
                return true;
        } catch (MalformedObjectNameException x) {
            // should not happen
            throw new IllegalArgumentException(String.valueOf(pattern), x);
        }
        return false;
    }

    @Override
    final QueryInterceptor getInterceptorForQuery(ObjectName pattern) {

        // Check if we need to aggregate.
        if (multipleQuery(pattern))
            return new AggregatingQueryInterceptor(this);

        // We don't need to aggregate: do the "simple" thing...
        final String domain = pattern.getDomain();

        // Do we have a virtual domain?
        final DomainInterceptor ns = getInterceptor(domain);
        if (ns != null) {
            if (LOG.isLoggable(Level.FINER))
                LOG.finer("dispatching to domain: " + domain);
            return new QueryInterceptor(ns);
        }

        // We don't have a virtual domain. Send to local domains.
        if (LOG.isLoggable(Level.FINER))
             LOG.finer("dispatching to local namespace: " + domain);
        return new QueryInterceptor(localNamespace);
    }

    @Override
    final ObjectName getHandlerNameFor(String key)
        throws MalformedObjectNameException {
        return JMXDomain.getDomainObjectName(key);
    }

    @Override
    final public String getHandlerKey(ObjectName name) {
        return name.getDomain();
    }

    @Override
    final DomainInterceptor createInterceptorFor(String key,
            ObjectName name, JMXDomain handler,
            Queue<Runnable> postRegisterQueue) {
        final DomainInterceptor ns =
                new DomainInterceptor(mbeanServerName,handler,key);
        ns.addPostRegisterTask(postRegisterQueue, delegate);
        if (LOG.isLoggable(Level.FINER)) {
            LOG.finer("DomainInterceptor created: "+ns);
        }
        return ns;
    }

    @Override
    final void interceptorReleased(DomainInterceptor interceptor,
            Queue<Runnable> postDeregisterQueue) {
        interceptor.addPostDeregisterTask(postDeregisterQueue, delegate);
    }

    @Override
    final DefaultMBeanServerInterceptor getNextInterceptor() {
        return localNamespace;
    }

    /**
     * Returns the list of domains in which any MBean is currently
     * registered.
     */
    @Override
    public String[] getDomains() {
        // A JMXDomain is registered in its own domain.
        // Therefore, localNamespace.getDomains() contains all domains.
        // In addition, localNamespace will perform the necessary
        // MBeanPermission checks for getDomains().
        //
        return localNamespace.getDomains();
    }

    /**
     * Returns the number of MBeans registered in the MBean server.
     */
    @Override
    public Integer getMBeanCount() {
        int count = getNextInterceptor().getMBeanCount().intValue();
        final String[] keys = getKeys();
        for (String key:keys) {
            final MBeanServer mbs = getInterceptor(key);
            if (mbs == null) continue;
            count += mbs.getMBeanCount().intValue();
        }
        return Integer.valueOf(count);
    }
}