blob: 9e8625d1669ed57707c72af7653cdbfdcec4c8f9 [file] [log] [blame]
/*
* 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 java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Queue;
import java.util.Set;
import javax.management.Attribute;
import javax.management.AttributeList;
import javax.management.AttributeNotFoundException;
import javax.management.InstanceAlreadyExistsException;
import javax.management.InstanceNotFoundException;
import javax.management.IntrospectionException;
import javax.management.InvalidAttributeValueException;
import javax.management.ListenerNotFoundException;
import javax.management.MBeanException;
import javax.management.MBeanInfo;
import javax.management.MBeanRegistrationException;
import javax.management.MBeanServer;
import javax.management.MalformedObjectNameException;
import javax.management.NotCompliantMBeanException;
import javax.management.NotificationFilter;
import javax.management.NotificationListener;
import javax.management.ObjectInstance;
import javax.management.ObjectName;
import javax.management.QueryExp;
import javax.management.ReflectionException;
import javax.management.namespace.JMXNamespace;
/**
* A dispatcher that dispatches to MBeanServers.
* <p><b>
* This API is a Sun internal API and is subject to changes without notice.
* </b></p>
* @since 1.7
*/
//
// This is the base class for implementing dispatchers. We have two concrete
// dispatcher implementations:
//
// * A NamespaceDispatchInterceptor, which dispatch calls to existing
// namespace interceptors
// * A DomainDispatchInterceptor, which dispatch calls to existing domain
// interceptors.
//
// With the JMX Namespaces feature, the JMX MBeanServer is now structured
// as follows:
//
// The JMX MBeanServer delegates to a NamespaceDispatchInterceptor,
// which either dispatches to a namespace, or delegates to the
// DomainDispatchInterceptor (if the object name contained no namespace).
// The DomainDispatchInterceptor in turn either dispatches to a domain (if
// there is a JMXDomain for that domain) or delegates to the
// DefaultMBeanServerInterceptor (if there is no JMXDomain for that
// domain). This makes the following picture:
//
// JMX MBeanServer (outer shell)
// |
// |
// NamespaceDispatchInterceptor
// / \
// no namespace in object name? \
// / \
// / dispatch to namespace
// DomainDispatchInterceptor
// / \
// no JMXDomain for domain? \
// / \
// / dispatch to domain
// DefaultMBeanServerInterceptor
// /
// invoke locally registered MBean
//
// The logic for maintaining a map of interceptors
// and dispatching to impacted interceptor, is implemented in this
// base class, which both NamespaceDispatchInterceptor and
// DomainDispatchInterceptor extend.
//
public abstract class DispatchInterceptor
<T extends MBeanServer, N extends JMXNamespace>
extends MBeanServerInterceptorSupport {
/**
* This is an abstraction which allows us to handle queryNames
* and queryMBeans with the same algorithm. There are some subclasses
* where we need to override both queryNames & queryMBeans to apply
* the same transformation (usually aggregation of results when
* several namespaces/domains are impacted) to both algorithms.
* Usually the only thing that varies between the algorithm of
* queryNames & the algorithm of queryMBean is the type of objects
* in the returned Set. By using a QueryInvoker we can implement the
* transformation only once and apply it to both queryNames &
* queryMBeans.
* @see QueryInterceptor below, and its subclass in
* {@link DomainDispatcher}.
**/
static abstract class QueryInvoker<T> {
abstract Set<T> query(MBeanServer mbs,
ObjectName pattern, QueryExp query);
}
/**
* Used to perform queryNames. A QueryInvoker that invokes
* queryNames on an MBeanServer.
**/
final static QueryInvoker<ObjectName> queryNamesInvoker =
new QueryInvoker<ObjectName>() {
Set<ObjectName> query(MBeanServer mbs,
ObjectName pattern, QueryExp query) {
return mbs.queryNames(pattern,query);
}
};
/**
* Used to perform queryMBeans. A QueryInvoker that invokes
* queryMBeans on an MBeanServer.
**/
final static QueryInvoker<ObjectInstance> queryMBeansInvoker =
new QueryInvoker<ObjectInstance>() {
Set<ObjectInstance> query(MBeanServer mbs,
ObjectName pattern, QueryExp query) {
return mbs.queryMBeans(pattern,query);
}
};
/**
* We use this class to intercept queries.
* There's a special case for JMXNamespace MBeans, because
* "namespace//*:*" matches both "namespace//domain:k=v" and
* "namespace//:type=JMXNamespace".
* Therefore, queries may need to be forwarded to more than
* on interceptor and the results aggregated...
*/
static class QueryInterceptor {
final MBeanServer wrapped;
QueryInterceptor(MBeanServer mbs) {
wrapped = mbs;
}
<X> Set<X> query(ObjectName pattern, QueryExp query,
QueryInvoker<X> invoker, MBeanServer server) {
return invoker.query(server, pattern, query);
}
public Set<ObjectName> queryNames(ObjectName pattern, QueryExp query) {
return query(pattern,query,queryNamesInvoker,wrapped);
}
public Set<ObjectInstance> queryMBeans(ObjectName pattern,
QueryExp query) {
return query(pattern,query,queryMBeansInvoker,wrapped);
}
}
// We don't need a ConcurrentHashMap here because getkeys() returns
// an array of keys. Therefore there's no risk to have a
// ConcurrentModificationException. We must however take into
// account the fact that there can be no interceptor for
// some of the returned keys if the map is being modified by
// another thread, or by a callback within the same thread...
// See getKeys() in this class and query() in DomainDispatcher.
//
private final Map<String,T> handlerMap =
Collections.synchronizedMap(
new HashMap<String,T>());
// The key at which an interceptor for accessing the named MBean can be
// found in the handlerMap. Note: there doesn't need to be an interceptor
// for that key in the Map.
//
public abstract String getHandlerKey(ObjectName name);
// Returns an interceptor for that name, or null if there's no interceptor
// for that name.
abstract MBeanServer getInterceptorOrNullFor(ObjectName name);
// Returns a QueryInterceptor for that pattern.
abstract QueryInterceptor getInterceptorForQuery(ObjectName pattern);
// Returns the ObjectName of the JMXNamespace (or JMXDomain) for that
// key (a namespace or a domain name).
abstract ObjectName getHandlerNameFor(String key)
throws MalformedObjectNameException;
// Creates an interceptor for the given key, name, JMXNamespace (or
// JMXDomain). Note: this will be either a NamespaceInterceptor
// wrapping a JMXNamespace, if this object is an instance of
// NamespaceDispatchInterceptor, or a DomainInterceptor wrapping a
// JMXDomain, if this object is an instance of DomainDispatchInterceptor.
abstract T createInterceptorFor(String key, ObjectName name,
N jmxNamespace, Queue<Runnable> postRegisterQueue);
//
// The next interceptor in the chain.
//
// For the NamespaceDispatchInterceptor, this the DomainDispatchInterceptor.
// For the DomainDispatchInterceptor, this is the
// DefaultMBeanServerInterceptor.
//
// The logic of when to invoke the next interceptor in the chain depends
// on the logic of the concrete dispatcher class.
//
// For instance, the NamespaceDispatchInterceptor invokes the next
// interceptor when the object name doesn't contain any namespace.
//
// On the other hand, the DomainDispatchInterceptor invokes the
// next interceptor when there's no interceptor for the accessed domain.
//
abstract MBeanServer getNextInterceptor();
// hook for cleanup in subclasses.
void interceptorReleased(T interceptor,
Queue<Runnable> postDeregisterQueue) {
// hook
}
// Hook for subclasses.
MBeanServer getInterceptorForCreate(ObjectName name)
throws MBeanRegistrationException {
final MBeanServer ns = getInterceptorOrNullFor(name);
if (ns == null) // name cannot be null here.
throw new MBeanRegistrationException(
new IllegalArgumentException("No such MBean handler: " +
getHandlerKey(name) + " for " +name));
return ns;
}
// Hook for subclasses.
MBeanServer getInterceptorForInstance(ObjectName name)
throws InstanceNotFoundException {
final MBeanServer ns = getInterceptorOrNullFor(name);
if (ns == null) // name cannot be null here.
throw new InstanceNotFoundException(String.valueOf(name));
return ns;
}
// sanity checks
void validateHandlerNameFor(String key, ObjectName name) {
if (key == null || key.equals(""))
throw new IllegalArgumentException("invalid key for "+name+": "+key);
try {
final ObjectName handlerName = getHandlerNameFor(key);
if (!name.equals(handlerName))
throw new IllegalArgumentException("bad handler name: "+name+
". Should be: "+handlerName);
} catch (MalformedObjectNameException x) {
throw new IllegalArgumentException(name.toString(),x);
}
}
// Called by the DefaultMBeanServerInterceptor when an instance
// of JMXNamespace (or a subclass of it) is registered as an MBean.
// This method is usually invoked from within the repository lock,
// hence the necessity of the postRegisterQueue.
public void addNamespace(ObjectName name, N jmxNamespace,
Queue<Runnable> postRegisterQueue) {
final String key = getHandlerKey(name);
validateHandlerNameFor(key,name);
synchronized (handlerMap) {
final T exists =
handlerMap.get(key);
if (exists != null)
throw new IllegalArgumentException(key+
": handler already exists");
final T ns = createInterceptorFor(key,name,jmxNamespace,
postRegisterQueue);
handlerMap.put(key,ns);
}
}
// Called by the DefaultMBeanServerInterceptor when an instance
// of JMXNamespace (or a subclass of it) is deregistered.
// This method is usually invoked from within the repository lock,
// hence the necessity of the postDeregisterQueue.
public void removeNamespace(ObjectName name, N jmxNamespace,
Queue<Runnable> postDeregisterQueue) {
final String key = getHandlerKey(name);
final T ns;
synchronized(handlerMap) {
ns = handlerMap.remove(key);
}
interceptorReleased(ns,postDeregisterQueue);
}
// Get the interceptor for that key.
T getInterceptor(String key) {
synchronized (handlerMap) {
return handlerMap.get(key);
}
}
// We return an array of keys, which makes it possible to make
// concurrent modifications of the handlerMap, provided that
// the code which loops over the keys is prepared to handle null
// interceptors.
// See declaration of handlerMap above, and see also query() in
// DomainDispatcher
//
public String[] getKeys() {
synchronized (handlerMap) {
final int size = handlerMap.size();
return handlerMap.keySet().toArray(new String[size]);
}
}
// From MBeanServer
public ObjectInstance createMBean(String className, ObjectName name)
throws ReflectionException, InstanceAlreadyExistsException,
MBeanRegistrationException, MBeanException,
NotCompliantMBeanException {
return getInterceptorForCreate(name).createMBean(className,name);
}
// From MBeanServer
public ObjectInstance createMBean(String className, ObjectName name,
ObjectName loaderName)
throws ReflectionException, InstanceAlreadyExistsException,
MBeanRegistrationException, MBeanException,
NotCompliantMBeanException, InstanceNotFoundException{
return getInterceptorForCreate(name).createMBean(className,name,loaderName);
}
// From MBeanServer
public ObjectInstance createMBean(String className, ObjectName name,
Object params[], String signature[])
throws ReflectionException, InstanceAlreadyExistsException,
MBeanRegistrationException, MBeanException,
NotCompliantMBeanException{
return getInterceptorForCreate(name).
createMBean(className,name,params,signature);
}
// From MBeanServer
public ObjectInstance createMBean(String className, ObjectName name,
ObjectName loaderName, Object params[],
String signature[])
throws ReflectionException, InstanceAlreadyExistsException,
MBeanRegistrationException, MBeanException,
NotCompliantMBeanException, InstanceNotFoundException{
return getInterceptorForCreate(name).createMBean(className,name,loaderName,
params,signature);
}
// From MBeanServer
public ObjectInstance registerMBean(Object object, ObjectName name)
throws InstanceAlreadyExistsException, MBeanRegistrationException,
NotCompliantMBeanException {
return getInterceptorForCreate(name).registerMBean(object,name);
}
// From MBeanServer
public void unregisterMBean(ObjectName name)
throws InstanceNotFoundException, MBeanRegistrationException {
getInterceptorForInstance(name).unregisterMBean(name);
}
// From MBeanServer
public ObjectInstance getObjectInstance(ObjectName name)
throws InstanceNotFoundException {
return getInterceptorForInstance(name).getObjectInstance(name);
}
// From MBeanServer
public Set<ObjectInstance> queryMBeans(ObjectName name, QueryExp query) {
final QueryInterceptor mbs =
getInterceptorForQuery(name);
if (mbs == null) return Collections.emptySet();
else return mbs.queryMBeans(name,query);
}
// From MBeanServer
public Set<ObjectName> queryNames(ObjectName name, QueryExp query) {
final QueryInterceptor mbs =
getInterceptorForQuery(name);
if (mbs == null) return Collections.emptySet();
else return mbs.queryNames(name,query);
}
// From MBeanServer
public boolean isRegistered(ObjectName name) {
final MBeanServer mbs = getInterceptorOrNullFor(name);
if (mbs == null) return false;
else return mbs.isRegistered(name);
}
// From MBeanServer
public Integer getMBeanCount() {
return getNextInterceptor().getMBeanCount();
}
// From MBeanServer
public Object getAttribute(ObjectName name, String attribute)
throws MBeanException, AttributeNotFoundException,
InstanceNotFoundException, ReflectionException {
return getInterceptorForInstance(name).getAttribute(name,attribute);
}
// From MBeanServer
public AttributeList getAttributes(ObjectName name, String[] attributes)
throws InstanceNotFoundException, ReflectionException {
return getInterceptorForInstance(name).getAttributes(name,attributes);
}
// From MBeanServer
public void setAttribute(ObjectName name, Attribute attribute)
throws InstanceNotFoundException, AttributeNotFoundException,
InvalidAttributeValueException, MBeanException,
ReflectionException {
getInterceptorForInstance(name).setAttribute(name,attribute);
}
// From MBeanServer
public AttributeList setAttributes(ObjectName name,
AttributeList attributes)
throws InstanceNotFoundException, ReflectionException {
return getInterceptorForInstance(name).setAttributes(name,attributes);
}
// From MBeanServer
public Object invoke(ObjectName name, String operationName,
Object params[], String signature[])
throws InstanceNotFoundException, MBeanException,
ReflectionException {
return getInterceptorForInstance(name).invoke(name,operationName,params,
signature);
}
// From MBeanServer
public String getDefaultDomain() {
return getNextInterceptor().getDefaultDomain();
}
/**
* Returns the list of domains in which any MBean is currently
* registered.
*/
public abstract String[] getDomains();
// From MBeanServer
public void addNotificationListener(ObjectName name,
NotificationListener listener,
NotificationFilter filter,
Object handback)
throws InstanceNotFoundException {
getInterceptorForInstance(name).addNotificationListener(name,listener,filter,
handback);
}
// From MBeanServer
public void addNotificationListener(ObjectName name,
ObjectName listener,
NotificationFilter filter,
Object handback)
throws InstanceNotFoundException {
getInterceptorForInstance(name).addNotificationListener(name,listener,filter,
handback);
}
// From MBeanServer
public void removeNotificationListener(ObjectName name,
ObjectName listener)
throws InstanceNotFoundException, ListenerNotFoundException {
getInterceptorForInstance(name).removeNotificationListener(name,listener);
}
// From MBeanServer
public void removeNotificationListener(ObjectName name,
ObjectName listener,
NotificationFilter filter,
Object handback)
throws InstanceNotFoundException, ListenerNotFoundException {
getInterceptorForInstance(name).removeNotificationListener(name,listener,filter,
handback);
}
// From MBeanServer
public void removeNotificationListener(ObjectName name,
NotificationListener listener)
throws InstanceNotFoundException, ListenerNotFoundException {
getInterceptorForInstance(name).removeNotificationListener(name,listener);
}
// From MBeanServer
public void removeNotificationListener(ObjectName name,
NotificationListener listener,
NotificationFilter filter,
Object handback)
throws InstanceNotFoundException, ListenerNotFoundException {
getInterceptorForInstance(name).removeNotificationListener(name,listener,filter,
handback);
}
// From MBeanServer
public MBeanInfo getMBeanInfo(ObjectName name)
throws InstanceNotFoundException, IntrospectionException,
ReflectionException {
return getInterceptorForInstance(name).getMBeanInfo(name);
}
// From MBeanServer
public boolean isInstanceOf(ObjectName name, String className)
throws InstanceNotFoundException {
return getInterceptorForInstance(name).isInstanceOf(name,className);
}
// From MBeanServer
public ClassLoader getClassLoaderFor(ObjectName mbeanName)
throws InstanceNotFoundException {
return getInterceptorForInstance(mbeanName).getClassLoaderFor(mbeanName);
}
// From MBeanServer
public ClassLoader getClassLoader(ObjectName loaderName)
throws InstanceNotFoundException {
return getInterceptorForInstance(loaderName).getClassLoader(loaderName);
}
}