提交 9d04c888 编写于 作者: E emcmanus

5072267: A way to communicate client context such as locale to the JMX server

Summary: Support for client contexts and also for localization of descriptions
Reviewed-by: dfuchs
上级 bacccfde
......@@ -69,9 +69,9 @@ public class ServiceName {
/**
* The version of the JMX specification implemented by this product.
* <BR>
* The value is <CODE>1.4</CODE>.
* The value is <CODE>2.0</CODE>.
*/
public static final String JMX_SPEC_VERSION = "1.4";
public static final String JMX_SPEC_VERSION = "2.0";
/**
* The vendor of the JMX specification implemented by this product.
......
......@@ -41,7 +41,7 @@ public class EventParams {
@SuppressWarnings("cast") // cast for jdk 1.5
public static long getLeaseTimeout() {
long timeout = EventClient.DEFAULT_LEASE_TIMEOUT;
long timeout = EventClient.DEFAULT_REQUESTED_LEASE_TIME;
try {
final GetPropertyAction act =
new GetPropertyAction(DEFAULT_LEASE_TIMEOUT);
......
......@@ -29,6 +29,7 @@ import com.sun.jmx.remote.util.ClassLogger;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
/**
......@@ -143,9 +144,10 @@ public class LeaseManager {
private final Runnable callback;
private ScheduledFuture<?> scheduled; // If null, the lease has expired.
private static final ThreadFactory threadFactory =
new DaemonThreadFactory("JMX LeaseManager %d");
private final ScheduledExecutorService executor
= Executors.newScheduledThreadPool(1,
new DaemonThreadFactory("JMX LeaseManager %d"));
= Executors.newScheduledThreadPool(1, threadFactory);
private static final ClassLogger logger =
new ClassLogger("javax.management.event", "LeaseManager");
......
......@@ -55,9 +55,19 @@ import javax.management.namespace.JMXNamespaces;
import javax.management.namespace.MBeanServerSupport;
import javax.management.remote.IdentityMBeanServerForwarder;
/**
* <p>An {@link MBeanServerForwarder} that simulates the existence of a
* given MBean. Requests for that MBean, call it X, are intercepted by the
* forwarder, and requests for any other MBean are forwarded to the next
* forwarder in the chain. Requests such as queryNames which can span both the
* X and other MBeans are handled by merging the results for X with the results
* from the next forwarder, unless the "visible" parameter is false, in which
* case X is invisible to such requests.</p>
*/
public class SingleMBeanForwarder extends IdentityMBeanServerForwarder {
private final ObjectName mbeanName;
private final boolean visible;
private DynamicMBean mbean;
private MBeanServer mbeanMBS = new MBeanServerSupport() {
......@@ -85,10 +95,20 @@ public class SingleMBeanForwarder extends IdentityMBeanServerForwarder {
return null;
}
// This will only be called if mbeanName has an empty domain.
// In that case a getAttribute (e.g.) of that name will have the
// domain replaced by MBeanServerSupport with the default domain,
// so we must be sure that the default domain is empty too.
@Override
public String getDefaultDomain() {
return mbeanName.getDomain();
}
};
public SingleMBeanForwarder(ObjectName mbeanName, DynamicMBean mbean) {
public SingleMBeanForwarder(
ObjectName mbeanName, DynamicMBean mbean, boolean visible) {
this.mbeanName = mbeanName;
this.visible = visible;
setSingleMBean(mbean);
}
......@@ -213,8 +233,10 @@ public class SingleMBeanForwarder extends IdentityMBeanServerForwarder {
@Override
public String[] getDomains() {
TreeSet<String> domainSet =
new TreeSet<String>(Arrays.asList(super.getDomains()));
String[] domains = super.getDomains();
if (!visible)
return domains;
TreeSet<String> domainSet = new TreeSet<String>(Arrays.asList(domains));
domainSet.add(mbeanName.getDomain());
return domainSet.toArray(new String[domainSet.size()]);
}
......@@ -222,7 +244,7 @@ public class SingleMBeanForwarder extends IdentityMBeanServerForwarder {
@Override
public Integer getMBeanCount() {
Integer count = super.getMBeanCount();
if (!super.isRegistered(mbeanName))
if (visible && !super.isRegistered(mbeanName))
count++;
return count;
}
......@@ -284,7 +306,7 @@ public class SingleMBeanForwarder extends IdentityMBeanServerForwarder {
*/
private boolean applies(ObjectName pattern) {
// we know pattern is not null.
if (!pattern.apply(mbeanName))
if (!visible || !pattern.apply(mbeanName))
return false;
final String dompat = pattern.getDomain();
......@@ -306,10 +328,12 @@ public class SingleMBeanForwarder extends IdentityMBeanServerForwarder {
@Override
public Set<ObjectInstance> queryMBeans(ObjectName name, QueryExp query) {
Set<ObjectInstance> names = super.queryMBeans(name, query);
if (name == null || applies(name) ) {
// Don't assume mbs.queryNames returns a writable set.
names = Util.cloneSet(names);
names.addAll(mbeanMBS.queryMBeans(name, query));
if (visible) {
if (name == null || applies(name) ) {
// Don't assume mbs.queryNames returns a writable set.
names = Util.cloneSet(names);
names.addAll(mbeanMBS.queryMBeans(name, query));
}
}
return names;
}
......@@ -317,10 +341,12 @@ public class SingleMBeanForwarder extends IdentityMBeanServerForwarder {
@Override
public Set<ObjectName> queryNames(ObjectName name, QueryExp query) {
Set<ObjectName> names = super.queryNames(name, query);
if (name == null || applies(name)) {
// Don't assume mbs.queryNames returns a writable set.
names = Util.cloneSet(names);
names.addAll(mbeanMBS.queryNames(name, query));
if (visible) {
if (name == null || applies(name)) {
// Don't assume mbs.queryNames returns a writable set.
names = Util.cloneSet(names);
names.addAll(mbeanMBS.queryNames(name, query));
}
}
return names;
}
......
......@@ -122,7 +122,7 @@ public final class JmxMBeanServer
* {@link javax.management.MBeanServerFactory#newMBeanServer(java.lang.String)}
* instead.
* <p>
* By default, {@link MBeanServerInterceptor} are disabled. Use
* By default, interceptors are disabled. Use
* {@link #JmxMBeanServer(java.lang.String,javax.management.MBeanServer,javax.management.MBeanServerDelegate,boolean)} to enable them.
* </ul>
* @param domain The default domain name used by this MBeanServer.
......@@ -239,7 +239,7 @@ public final class JmxMBeanServer
this.mBeanServerDelegateObject = delegate;
this.outerShell = outer;
final Repository repository = new Repository(domain,fairLock);
final Repository repository = new Repository(domain);
this.mbsInterceptor =
new NamespaceDispatchInterceptor(outer, delegate, instantiator,
repository);
......
/*
* 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.namespace;
import com.sun.jmx.defaults.JmxProperties;
import java.io.IOException;
import java.util.Collections;
import java.util.Map;
import java.util.WeakHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.management.ListenerNotFoundException;
import javax.management.MBeanServerConnection;
import javax.management.NotificationFilter;
import javax.management.NotificationListener;
import javax.management.event.EventClient;
import javax.management.event.EventClientDelegateMBean;
import javax.management.namespace.JMXNamespace;
import javax.management.namespace.JMXNamespaces;
import javax.management.remote.JMXAddressable;
import javax.management.remote.JMXConnector;
import javax.management.remote.JMXServiceURL;
import javax.security.auth.Subject;
/**
* A collection of methods that provide JMXConnector wrappers for
* JMXRemoteNamepaces underlying connectors.
* <p><b>
* This API is a Sun internal API and is subject to changes without notice.
* </b></p>
* @since 1.7
*/
public final class JMXNamespaceUtils {
/**
* A logger for this class.
**/
private static final Logger LOG = JmxProperties.NAMESPACE_LOGGER;
private static <K,V> Map<K,V> newWeakHashMap() {
return new WeakHashMap<K,V>();
}
/** There are no instances of this class */
private JMXNamespaceUtils() {
}
// returns un unmodifiable view of a map.
public static <K,V> Map<K,V> unmodifiableMap(Map<K,V> aMap) {
if (aMap == null || aMap.isEmpty())
return Collections.emptyMap();
return Collections.unmodifiableMap(aMap);
}
/**
* A base class that helps writing JMXConnectors that return
* MBeanServerConnection wrappers.
* This base class wraps an inner JMXConnector (the source), and preserve
* its caching policy. If a connection is cached in the source, its wrapper
* will be cached in this connector too.
* Author's note: rewriting this with java.lang.reflect.Proxy could be
* envisaged. It would avoid the combinatory sub-classing introduced by
* JMXAddressable.
* <p>
* Note: all the standard JMXConnector implementations are serializable.
* This implementation here is not. Should it be?
* I believe it must not be serializable unless it becomes
* part of a public API (either standard or officially exposed
* and supported in a documented com.sun package)
**/
static class JMXCachingConnector
implements JMXConnector {
// private static final long serialVersionUID = -2279076110599707875L;
final JMXConnector source;
// if this object is made serializable, then the variable below
// needs to become volatile transient and be lazyly-created...
private final
Map<MBeanServerConnection,MBeanServerConnection> connectionMap;
public JMXCachingConnector(JMXConnector source) {
this.source = checkNonNull(source, "source");
connectionMap = newWeakHashMap();
}
private MBeanServerConnection
getCached(MBeanServerConnection inner) {
return connectionMap.get(inner);
}
private MBeanServerConnection putCached(final MBeanServerConnection inner,
final MBeanServerConnection wrapper) {
if (inner == wrapper) return wrapper;
synchronized (this) {
final MBeanServerConnection concurrent =
connectionMap.get(inner);
if (concurrent != null) return concurrent;
connectionMap.put(inner,wrapper);
}
return wrapper;
}
public void addConnectionNotificationListener(NotificationListener
listener, NotificationFilter filter, Object handback) {
source.addConnectionNotificationListener(listener,filter,handback);
}
public void close() throws IOException {
source.close();
}
public void connect() throws IOException {
source.connect();
}
public void connect(Map<String,?> env) throws IOException {
source.connect(env);
}
public String getConnectionId() throws IOException {
return source.getConnectionId();
}
/**
* Preserve caching policy of the underlying connector.
**/
public MBeanServerConnection
getMBeanServerConnection() throws IOException {
final MBeanServerConnection inner =
source.getMBeanServerConnection();
final MBeanServerConnection cached = getCached(inner);
if (cached != null) return cached;
final MBeanServerConnection wrapper = wrap(inner);
return putCached(inner,wrapper);
}
public MBeanServerConnection
getMBeanServerConnection(Subject delegationSubject)
throws IOException {
final MBeanServerConnection wrapped =
source.getMBeanServerConnection(delegationSubject);
synchronized (this) {
final MBeanServerConnection cached = getCached(wrapped);
if (cached != null) return cached;
final MBeanServerConnection wrapper =
wrapWithSubject(wrapped,delegationSubject);
return putCached(wrapped,wrapper);
}
}
public void removeConnectionNotificationListener(
NotificationListener listener)
throws ListenerNotFoundException {
source.removeConnectionNotificationListener(listener);
}
public void removeConnectionNotificationListener(
NotificationListener l, NotificationFilter f,
Object handback) throws ListenerNotFoundException {
source.removeConnectionNotificationListener(l,f,handback);
}
/**
* This is the method that subclass will redefine. This method
* is called by {@code this.getMBeanServerConnection()}.
* {@code inner} is the connection returned by
* {@code source.getMBeanServerConnection()}.
**/
protected MBeanServerConnection wrap(MBeanServerConnection inner)
throws IOException {
return inner;
}
/**
* Subclass may also want to redefine this method.
* By default it calls wrap(inner). This method
* is called by {@code this.getMBeanServerConnection(Subject)}.
* {@code inner} is the connection returned by
* {@code source.getMBeanServerConnection(Subject)}.
**/
protected MBeanServerConnection wrapWithSubject(
MBeanServerConnection inner, Subject delegationSubject)
throws IOException {
return wrap(inner);
}
@Override
public String toString() {
if (source instanceof JMXAddressable) {
final JMXServiceURL address =
((JMXAddressable)source).getAddress();
if (address != null)
return address.toString();
}
return source.toString();
}
}
/**
* The name space connector can do 'cd'
**/
static class JMXNamespaceConnector extends JMXCachingConnector {
// private static final long serialVersionUID = -4813611540843020867L;
private final String toDir;
private final boolean closeable;
public JMXNamespaceConnector(JMXConnector source, String toDir,
boolean closeable) {
super(source);
this.toDir = toDir;
this.closeable = closeable;
}
@Override
public void close() throws IOException {
if (!closeable)
throw new UnsupportedOperationException("close");
else super.close();
}
@Override
protected MBeanServerConnection wrap(MBeanServerConnection wrapped)
throws IOException {
if (LOG.isLoggable(Level.FINER))
LOG.finer("Creating name space proxy connection for source: "+
"namespace="+toDir);
return JMXNamespaces.narrowToNamespace(wrapped,toDir);
}
@Override
public String toString() {
return "JMXNamespaces.narrowToNamespace("+
super.toString()+
", \""+toDir+"\")";
}
}
static class JMXEventConnector extends JMXCachingConnector {
// private static final long serialVersionUID = 4742659236340242785L;
JMXEventConnector(JMXConnector wrapped) {
super(wrapped);
}
@Override
protected MBeanServerConnection wrap(MBeanServerConnection inner)
throws IOException {
return EventClient.getEventClientConnection(inner);
}
@Override
public String toString() {
return "EventClient.withEventClient("+super.toString()+")";
}
}
static class JMXAddressableEventConnector extends JMXEventConnector
implements JMXAddressable {
// private static final long serialVersionUID = -9128520234812124712L;
JMXAddressableEventConnector(JMXConnector wrapped) {
super(wrapped);
}
public JMXServiceURL getAddress() {
return ((JMXAddressable)source).getAddress();
}
}
/**
* Creates a connector whose MBeamServerConnection will point to the
* given sub name space inside the source connector.
* @see JMXNamespace
**/
public static JMXConnector cd(final JMXConnector source,
final String toNamespace,
final boolean closeable)
throws IOException {
checkNonNull(source, "JMXConnector");
if (toNamespace == null || toNamespace.equals(""))
return source;
return new JMXNamespaceConnector(source,toNamespace,closeable);
}
/**
* Returns a JMX Connector that will use an {@link EventClient}
* to subscribe for notifications. If the server doesn't have
* an {@link EventClientDelegateMBean}, then the connector will
* use the legacy notification mechanism instead.
*
* @param source The underlying JMX Connector wrapped by the returned
* connector.
* @return A JMX Connector that will uses an {@link EventClient}, if
* available.
* @see EventClient#getEventClientConnection(MBeanServerConnection)
*/
public static JMXConnector withEventClient(final JMXConnector source) {
checkNonNull(source, "JMXConnector");
if (source instanceof JMXAddressable)
return new JMXAddressableEventConnector(source);
else
return new JMXEventConnector(source);
}
public static <T> T checkNonNull(T parameter, String name) {
if (parameter == null)
throw new IllegalArgumentException(name+" must not be null");
return parameter;
}
}
......@@ -49,11 +49,6 @@ public class ObjectNameRouter {
final int tlen;
final boolean identity;
public ObjectNameRouter(String targetDirName) {
this(targetDirName,null);
}
/** Creates a new instance of ObjectNameRouter */
public ObjectNameRouter(final String remove, final String add) {
this.targetPrefix = (remove==null?"":remove);
......@@ -186,6 +181,4 @@ public class ObjectNameRouter {
b.append(NAMESPACE_SEPARATOR);
return b.toString();
}
}
......@@ -31,7 +31,6 @@ import java.util.logging.Level;
import java.util.logging.Logger;
import javax.management.MBeanServerConnection;
import javax.management.namespace.JMXNamespaces;
/**
......@@ -57,22 +56,14 @@ public class RoutingConnectionProxy
private static final Logger LOG = JmxProperties.NAMESPACE_LOGGER;
/**
* Creates a new instance of RoutingConnectionProxy
*/
public RoutingConnectionProxy(MBeanServerConnection source,
String sourceDir) {
this(source,sourceDir,"",false);
}
/**
* Creates a new instance of RoutingConnectionProxy
*/
public RoutingConnectionProxy(MBeanServerConnection source,
String sourceDir,
String targetDir,
boolean forwardsContext) {
super(source,sourceDir,targetDir,forwardsContext);
boolean probe) {
super(source, sourceDir, targetDir, probe);
if (LOG.isLoggable(Level.FINER))
LOG.finer("RoutingConnectionProxy for " + getSourceNamespace() +
......@@ -85,15 +76,13 @@ public class RoutingConnectionProxy
final String sourceNs = getSourceNamespace();
String wrapped = String.valueOf(source());
if ("".equals(targetNs)) {
if (forwardsContext)
wrapped = "ClientContext.withDynamicContext("+wrapped+")";
return "JMXNamespaces.narrowToNamespace("+
wrapped+", \""+
sourceNs+"\")";
}
return this.getClass().getSimpleName()+"("+wrapped+", \""+
sourceNs+"\", \""+
targetNs+"\", "+forwardsContext+")";
targetNs+"\")";
}
static final RoutingProxyFactory
......@@ -102,22 +91,16 @@ public class RoutingConnectionProxy
<MBeanServerConnection,RoutingConnectionProxy>() {
public RoutingConnectionProxy newInstance(MBeanServerConnection source,
String sourcePath, String targetPath,
boolean forwardsContext) {
String sourcePath, String targetPath, boolean probe) {
return new RoutingConnectionProxy(source,sourcePath,
targetPath,forwardsContext);
}
public RoutingConnectionProxy newInstance(
MBeanServerConnection source, String sourcePath) {
return new RoutingConnectionProxy(source,sourcePath);
targetPath, probe);
}
};
public static MBeanServerConnection cd(MBeanServerConnection source,
String sourcePath) {
public static MBeanServerConnection cd(
MBeanServerConnection source, String sourcePath, boolean probe) {
return RoutingProxy.cd(RoutingConnectionProxy.class, FACTORY,
source, sourcePath);
source, sourcePath, probe);
}
}
......@@ -30,6 +30,7 @@ import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.management.InstanceNotFoundException;
import javax.management.MBeanException;
import javax.management.MBeanRegistrationException;
......@@ -90,17 +91,9 @@ import javax.management.namespace.JMXNamespaces;
// targetNs=<encoded-context> // context must be removed from object name
// sourceNs="" // nothing to add...
//
// RoutingProxies can also be used on the client side to implement
// "withClientContext" operations. In that case, the boolean parameter
// 'forwards context' is set to true, targetNs is "", and sourceNS may
// also be "". When forwardsContext is true, the RoutingProxy dynamically
// creates an ObjectNameRouter for each operation - in order to dynamically add
// the context attached to the thread to the routing ObjectName. This is
// performed in the getObjectNameRouter() method.
//
// Finally, in order to avoid too many layers of wrapping,
// RoutingConnectionProxy and RoutingServerProxy can be created through a
// factory method that can concatenate namespace pathes in order to
// factory method that can concatenate namespace paths in order to
// return a single RoutingProxy - rather than wrapping a RoutingProxy inside
// another RoutingProxy. See RoutingConnectionProxy.cd and
// RoutingServerProxy.cd
......@@ -146,25 +139,27 @@ public abstract class RoutingProxy<T extends MBeanServerConnection>
private final T source;
// The name space we're narrowing to (usually some name space in
// the source MBeanServerConnection
// the source MBeanServerConnection), e.g. "a" for the namespace
// "a//". This is empty in the case of ClientContext described above.
private final String sourceNs;
// The name space we pretend to be mounted in (usually "")
// The name space we pretend to be mounted in. This is empty except
// in the case of ClientContext described above (where it will be
// something like "jmx.context//foo=bar".
private final String targetNs;
// The name of the JMXNamespace that handles the source name space
private final ObjectName handlerName;
private final ObjectNameRouter router;
final boolean forwardsContext;
private volatile String defaultDomain = null;
/**
* Creates a new instance of RoutingProxy
*/
protected RoutingProxy(T source,
String sourceNs,
String targetNs,
boolean forwardsContext) {
String sourceNs,
String targetNs,
boolean probe) {
if (source == null) throw new IllegalArgumentException("null");
this.sourceNs = JMXNamespaces.normalizeNamespaceName(sourceNs);
......@@ -177,13 +172,17 @@ public abstract class RoutingProxy<T extends MBeanServerConnection>
// System.err.println("sourceNs: "+sourceNs);
this.handlerName =
JMXNamespaces.getNamespaceObjectName(this.sourceNs);
try {
// System.err.println("handlerName: "+handlerName);
if (!source.isRegistered(handlerName))
throw new IllegalArgumentException(sourceNs +
": no such name space");
} catch (IOException x) {
throw new IllegalArgumentException("source stale: "+x,x);
if (probe) {
try {
if (!source.isRegistered(handlerName)) {
InstanceNotFoundException infe =
new InstanceNotFoundException(handlerName);
throw new IllegalArgumentException(sourceNs +
": no such name space", infe);
}
} catch (IOException x) {
throw new IllegalArgumentException("source stale: "+x,x);
}
}
}
this.source = source;
......@@ -191,7 +190,6 @@ public abstract class RoutingProxy<T extends MBeanServerConnection>
JMXNamespaces.normalizeNamespaceName(targetNs));
this.router =
new ObjectNameRouter(this.targetNs,this.sourceNs);
this.forwardsContext = forwardsContext;
if (LOG.isLoggable(Level.FINER))
LOG.finer("RoutingProxy for " + this.sourceNs + " created");
......@@ -200,14 +198,6 @@ public abstract class RoutingProxy<T extends MBeanServerConnection>
@Override
public T source() { return source; }
ObjectNameRouter getObjectNameRouter() {
// TODO: uncomment this when contexts are added
// if (forwardsContext)
// return ObjectNameRouter.wrapWithContext(router);
// else
return router;
}
@Override
public ObjectName toSource(ObjectName targetName)
throws MalformedObjectNameException {
......@@ -222,8 +212,7 @@ public abstract class RoutingProxy<T extends MBeanServerConnection>
if (defaultDomain != null)
targetName = targetName.withDomain(defaultDomain);
}
final ObjectNameRouter r = getObjectNameRouter();
return r.toSourceContext(targetName,true);
return router.toSourceContext(targetName,true);
}
@Override
......@@ -243,8 +232,7 @@ public abstract class RoutingProxy<T extends MBeanServerConnection>
public ObjectName toTarget(ObjectName sourceName)
throws MalformedObjectNameException {
if (sourceName == null) return null;
final ObjectNameRouter r = getObjectNameRouter();
return r.toTargetContext(sourceName,false);
return router.toTargetContext(sourceName,false);
}
private Object getAttributeFromHandler(String attributeName)
......@@ -357,11 +345,8 @@ public abstract class RoutingProxy<T extends MBeanServerConnection>
// instance.
static interface RoutingProxyFactory<T extends MBeanServerConnection,
R extends RoutingProxy<T>> {
R newInstance(T source,
String sourcePath, String targetPath,
boolean forwardsContext);
R newInstance(T source,
String sourcePath);
public R newInstance(
T source, String sourcePath, String targetPath, boolean probe);
}
// Performs a narrowDownToNamespace operation.
......@@ -377,7 +362,7 @@ public abstract class RoutingProxy<T extends MBeanServerConnection>
static <T extends MBeanServerConnection, R extends RoutingProxy<T>>
R cd(Class<R> routingProxyClass,
RoutingProxyFactory<T,R> factory,
T source, String sourcePath) {
T source, String sourcePath, boolean probe) {
if (source == null) throw new IllegalArgumentException("null");
if (source.getClass().equals(routingProxyClass)) {
// cast is OK here, but findbugs complains unless we use class.cast
......@@ -400,14 +385,13 @@ public abstract class RoutingProxy<T extends MBeanServerConnection>
final String path =
JMXNamespaces.concat(other.getSourceNamespace(),
sourcePath);
return factory.newInstance(other.source(),path,"",
other.forwardsContext);
return factory.newInstance(other.source(), path, "", probe);
}
// Note: we could do possibly something here - but it would involve
// removing part of targetDir, and possibly adding
// something to sourcePath.
// Too complex to bother! => simply default to stacking...
}
return factory.newInstance(source,sourcePath);
return factory.newInstance(source, sourcePath, "", probe);
}
}
......@@ -54,7 +54,6 @@ import javax.management.OperationsException;
import javax.management.QueryExp;
import javax.management.ReflectionException;
import javax.management.loading.ClassLoaderRepository;
import javax.management.namespace.JMXNamespaces;
/**
* A RoutingServerProxy is an MBeanServer proxy that proxies a
......@@ -76,19 +75,11 @@ public class RoutingServerProxy
extends RoutingProxy<MBeanServer>
implements MBeanServer {
/**
* Creates a new instance of RoutingServerProxy
*/
public RoutingServerProxy(MBeanServer source,
String sourceNs) {
this(source,sourceNs,"",false);
}
public RoutingServerProxy(MBeanServer source,
String sourceNs,
String targetNs,
boolean forwardsContext) {
super(source,sourceNs,targetNs,forwardsContext);
boolean probe) {
super(source, sourceNs, targetNs, probe);
}
/**
......@@ -571,20 +562,15 @@ public class RoutingServerProxy
FACTORY = new RoutingProxyFactory<MBeanServer,RoutingServerProxy>() {
public RoutingServerProxy newInstance(MBeanServer source,
String sourcePath, String targetPath,
boolean forwardsContext) {
return new RoutingServerProxy(source,sourcePath,
targetPath,forwardsContext);
}
public RoutingServerProxy newInstance(
MBeanServer source, String sourcePath) {
return new RoutingServerProxy(source,sourcePath);
String sourcePath, String targetPath, boolean probe) {
return new RoutingServerProxy(
source, sourcePath, targetPath, probe);
}
};
public static MBeanServer cd(MBeanServer source, String sourcePath) {
public static MBeanServer cd(
MBeanServer source, String sourcePath, boolean probe) {
return RoutingProxy.cd(RoutingServerProxy.class, FACTORY,
source, sourcePath);
source, sourcePath, probe);
}
}
......@@ -430,13 +430,11 @@ public class EventClientConnection implements InvocationHandler,
* The {@code EventClient} is created lazily, when it is needed
* for the first time. If null, a default factory will be used
* (see {@link #createEventClient}).
* @return the
* @return the MBeanServerConnection.
**/
public static MBeanServerConnection getEventConnectionFor(
MBeanServerConnection connection,
Callable<EventClient> eventClientFactory) {
// if c already uses an EventClient no need to create a new one.
//
if (connection instanceof EventClientFactory
&& eventClientFactory != null)
throw new IllegalArgumentException("connection already uses EventClient");
......
此差异已折叠。
......@@ -35,8 +35,8 @@ import java.io.Serializable;
// Javadoc imports:
import java.lang.management.MemoryUsage;
import java.util.Arrays;
import java.util.Locale;
import java.util.ResourceBundle;
import javax.management.openmbean.CompositeData;
import javax.management.openmbean.MXBeanMappingFactory;
import javax.management.openmbean.OpenMBeanAttributeInfoSupport;
......@@ -118,19 +118,22 @@ import javax.management.openmbean.OpenType;
* deprecation, for example {@code "1.3 Replaced by the Capacity
* attribute"}.</td>
*
* <tr id="descriptionResourceBundleBaseName">
* <td>descriptionResource<br>BundleBaseName</td><td>String</td><td>Any</td>
* <tr><td id="descriptionResourceBundleBaseName"><i>descriptionResource<br>
* BundleBaseName</i></td><td>String</td><td>Any</td>
*
* <td>The base name for the {@link ResourceBundle} in which the key given in
* the {@code descriptionResourceKey} field can be found, for example
* {@code "com.example.myapp.MBeanResources"}.</td>
* {@code "com.example.myapp.MBeanResources"}. See
* {@link MBeanInfo#localizeDescriptions MBeanInfo.localizeDescriptions}.</td>
*
* <tr id="descriptionResourceKey">
* <td>descriptionResourceKey</td><td>String</td><td>Any</td>
* <tr><td id="descriptionResourceKey"><i>descriptionResourceKey</i></td>
* <td>String</td><td>Any</td>
*
* <td>A resource key for the description of this element. In
* conjunction with the {@code descriptionResourceBundleBaseName},
* this can be used to find a localized version of the description.</td>
* this can be used to find a localized version of the description.
* See {@link MBeanInfo#localizeDescriptions MBeanInfo.localizeDescriptions}.
* </td>
*
* <tr><td>enabled</td><td>String</td>
* <td>MBeanAttributeInfo<br>MBeanNotificationInfo<br>MBeanOperationInfo</td>
......@@ -157,11 +160,11 @@ import javax.management.openmbean.OpenType;
* href="MBeanInfo.html#info-changed">{@code "jmx.mbean.info.changed"}</a>
* notification.</td>
*
* <tr><td>infoTimeout</td><td>String<br>Long</td><td>MBeanInfo</td>
* <tr id="infoTimeout"><td>infoTimeout</td><td>String<br>Long</td><td>MBeanInfo</td>
*
* <td id="infoTimeout">The time in milli-seconds that the MBeanInfo can
* reasonably be expected to be unchanged. The value can be a {@code Long}
* or a decimal string. This provides a hint from a DynamicMBean or any
* <td>The time in milli-seconds that the MBeanInfo can reasonably be
* expected to be unchanged. The value can be a {@code Long} or a
* decimal string. This provides a hint from a DynamicMBean or any
* MBean that does not define {@code immutableInfo} as {@code true}
* that the MBeanInfo is not likely to change within this period and
* therefore can be cached. When this field is missing or has the
......@@ -185,6 +188,13 @@ import javax.management.openmbean.OpenType;
* <td>Legal values for an attribute or parameter. See
* {@link javax.management.openmbean}.</td>
*
* <tr id="locale"><td><i>locale</i></td>
* <td>String</td><td>Any</td>
*
* <td>The {@linkplain Locale locale} of the description in this
* {@code MBeanInfo}, {@code MBeanAttributeInfo}, etc, as returned
* by {@link Locale#toString()}.</td>
*
* <tr id="maxValue"><td><i>maxValue</i><td>Object</td>
* <td>MBeanAttributeInfo<br>MBeanParameterInfo</td>
*
......
......@@ -30,6 +30,7 @@ import com.sun.jmx.mbeanserver.MBeanInjector;
import com.sun.jmx.remote.util.ClassLogger;
import java.beans.BeanInfo;
import java.beans.PropertyDescriptor;
import java.io.IOException;
import java.io.Serializable;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
......@@ -37,6 +38,7 @@ import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Map;
import java.util.TreeMap;
import javax.management.namespace.JMXNamespaces;
import javax.management.openmbean.MXBeanMappingFactory;
/**
......@@ -60,6 +62,21 @@ public class JMX {
*/
public static final String DEFAULT_VALUE_FIELD = "defaultValue";
/**
* The name of the <a
* href="Descriptor.html#descriptionResourceBundleBaseName">{@code
* descriptionResourceBundleBaseName}</a> field.
*/
public static final String DESCRIPTION_RESOURCE_BUNDLE_BASE_NAME_FIELD =
"descriptionResourceBundleBaseName";
/**
* The name of the <a href="Descriptor.html#descriptionResourceKey">{@code
* descriptionResourceKey}</a> field.
*/
public static final String DESCRIPTION_RESOURCE_KEY_FIELD =
"descriptionResourceKey";
/**
* The name of the <a href="Descriptor.html#immutableInfo">{@code
* immutableInfo}</a> field.
......@@ -78,6 +95,12 @@ public class JMX {
*/
public static final String LEGAL_VALUES_FIELD = "legalValues";
/**
* The name of the <a href="Descriptor.html#locale">{@code locale}</a>
* field.
*/
public static final String LOCALE_FIELD = "locale";
/**
* The name of the <a href="Descriptor.html#maxValue">{@code
* maxValue}</a> field.
......@@ -120,13 +143,12 @@ public class JMX {
* <p>Options to apply to an MBean proxy or to an instance of {@link
* StandardMBean}.</p>
*
* <p>For example, to specify a custom {@link MXBeanMappingFactory}
* for a {@code StandardMBean}, you might write this:</p>
* <p>For example, to specify the "wrapped object visible" option for a
* {@code StandardMBean}, you might write this:</p>
*
* <pre>
* MXBeanMappingFactory factory = new MyMXBeanMappingFactory();
* JMX.MBeanOptions opts = new JMX.MBeanOptions();
* opts.setMXBeanMappingFactory(factory);
* StandardMBean.Options opts = new StandardMBean.Options();
* opts.setWrappedObjectVisible(true);
* StandardMBean mbean = new StandardMBean(impl, intf, opts);
* </pre>
*
......
......@@ -25,6 +25,7 @@
package javax.management;
import com.sun.jmx.mbeanserver.Util;
import java.io.IOException;
import java.io.StreamCorruptedException;
import java.io.Serializable;
......@@ -37,6 +38,12 @@ import java.util.WeakHashMap;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.HashMap;
import java.util.Locale;
import java.util.MissingResourceException;
import java.util.ResourceBundle;
import java.util.logging.Level;
import java.util.logging.Logger;
import static javax.management.ImmutableDescriptor.nonNullDescriptor;
/**
......@@ -290,6 +297,7 @@ public class MBeanInfo implements Cloneable, Serializable, DescriptorRead {
* <p>Since this class is immutable, the clone method is chiefly of
* interest to subclasses.</p>
*/
@Override
public Object clone () {
try {
return super.clone() ;
......@@ -474,6 +482,7 @@ public class MBeanInfo implements Cloneable, Serializable, DescriptorRead {
return (Descriptor) nonNullDescriptor(descriptor).clone();
}
@Override
public String toString() {
return
getClass().getName() + "[" +
......@@ -505,6 +514,7 @@ public class MBeanInfo implements Cloneable, Serializable, DescriptorRead {
* @return true if and only if <code>o</code> is an MBeanInfo that is equal
* to this one according to the rules above.
*/
@Override
public boolean equals(Object o) {
if (o == this)
return true;
......@@ -524,6 +534,7 @@ public class MBeanInfo implements Cloneable, Serializable, DescriptorRead {
Arrays.equals(p.fastGetNotifications(), fastGetNotifications()));
}
@Override
public int hashCode() {
/* Since computing the hashCode is quite expensive, we cache it.
If by some terrible misfortune the computed value is 0, the
......@@ -747,4 +758,377 @@ public class MBeanInfo implements Cloneable, Serializable, DescriptorRead {
throw new StreamCorruptedException("Got unexpected byte.");
}
}
/**
* <p>Return an {@code MBeanInfo} object that is the same as this one
* except that its descriptions are localized in the given locale.
* This means the text returned by {@link MBeanInfo#getDescription}
* (the description of the MBean itself), and the text returned by the
* {@link MBeanFeatureInfo#getDescription getDescription()} method
* for every {@linkplain MBeanAttributeInfo attribute}, {@linkplain
* MBeanOperationInfo operation}, {@linkplain MBeanConstructorInfo
* constructor}, and {@linkplain MBeanNotificationInfo notification}
* contained in the {@code MBeanInfo}.</p>
*
* <p>Here is how the description {@code this.getDescription()} is
* localized.</p>
*
* <p>First, if the {@linkplain #getDescriptor() descriptor}
* of this {@code MBeanInfo} contains a field <code><a
* href="Descriptor.html#locale">"locale"</a></code>, and the value of
* the field is the same as {@code locale.toString()}, then this {@code
* MBeanInfo} is returned. Otherwise, localization proceeds as follows,
* and the {@code "locale"} field in the returned {@code MBeanInfo} will
* be {@code locale.toString()}.
*
* <p>A <em>{@code className}</em> is determined. If this
* {@code MBeanInfo} contains a descriptor with the field
* <a href="Descriptor.html#interfaceClassName">{@code
* "interfaceClassName"}</a>, then the value of that field is the
* {@code className}. Otherwise, it is {@link #getClassName()}.
* Everything before the last period (.) in the {@code className} is
* the <em>{@code package}</em>, and everything after is the <em>{@code
* simpleClassName}</em>. (If there is no period, then the {@code package}
* is empty and the {@code simpleClassName} is the same as the {@code
* className}.)</p>
*
* <p>A <em>{@code resourceKey}</em> is determined. If this {@code
* MBeanInfo} contains a {@linkplain MBeanInfo#getDescriptor() descriptor}
* with a field {@link JMX#DESCRIPTION_RESOURCE_KEY_FIELD
* "descriptionResourceKey"}, the value of the field is
* the {@code resourceKey}. Otherwise, the {@code resourceKey} is {@code
* simpleClassName + ".mbean"}.</p>
*
* <p>A <em>{@code resourceBundleBaseName}</em> is determined. If
* this {@code MBeanInfo} contains a descriptor with a field {@link
* JMX#DESCRIPTION_RESOURCE_BUNDLE_BASE_NAME_FIELD
* "descriptionResourceBundleBaseName"}, the value of the field
* is the {@code resourceBundleBaseName}. Otherwise, the {@code
* resourceBundleBaseName} is {@code package + ".MBeanDescriptions"}.
*
* <p>Then, a {@link java.util.ResourceBundle ResourceBundle} is
* determined, using<br> {@link java.util.ResourceBundle#getBundle(String,
* Locale, ClassLoader) ResourceBundle.getBundle(resourceBundleBaseName,
* locale, loader)}. If this succeeds, and if {@link
* java.util.ResourceBundle#getString(String) getString(resourceKey)}
* returns a string, then that string is the localized description.
* Otherwise, the original description is unchanged.</p>
*
* <p>A localized description for an {@code MBeanAttributeInfo} is
* obtained similarly. The default {@code resourceBundleBaseName}
* is the same as above. The default description and the
* descriptor fields {@code "descriptionResourceKey"} and {@code
* "descriptionResourceBundleBaseName"} come from the {@code
* MBeanAttributeInfo} rather than the {@code MBeanInfo}. If the
* attribute's {@linkplain MBeanFeatureInfo#getName() name} is {@code
* Foo} then its default {@code resourceKey} is {@code simpleClassName +
* ".attribute.Foo"}.</p>
*
* <p>Similar rules apply for operations, constructors, and notifications.
* If the name of the operation, constructor, or notification is {@code
* Foo} then the default {@code resourceKey} is respectively {@code
* simpleClassName + ".operation.Foo"}, {@code simpleClassName +
* ".constructor.Foo"}, or {@code simpleClassName + ".notification.Foo"}.
* If two operations or constructors have the same name (overloading) then
* they have the same default {@code resourceKey}; if different localized
* descriptions are needed then a non-default key must be supplied using
* {@code "descriptionResourceKey"}.</p>
*
* <p>Similar rules also apply for descriptions of parameters ({@link
* MBeanParameterInfo}). The default {@code resourceKey} for a parameter
* whose {@linkplain MBeanFeatureInfo#getName() name} is {@code
* Bar} in an operation or constructor called {@code Foo} is {@code
* simpleClassName + ".operation.Foo.Bar"} or {@code simpleClassName +
* ".constructor.Foo.Bar"} respectively.</p>
*
* <h4>Example</h4>
*
* <p>Suppose you have an MBean defined by these two Java source files:</p>
*
* <pre>
* // ConfigurationMBean.java
* package com.example;
* public interface ConfigurationMBean {
* public String getName();
* public void save(String fileName);
* }
*
* // Configuration.java
* package com.example;
* public class Configuration implements ConfigurationMBean {
* public Configuration(String defaultName) {
* ...
* }
* ...
* }
* </pre>
*
* <p>Then you could define the default descriptions for the MBean, by
* including a resource bundle called {@code com/example/MBeanDescriptions}
* with the compiled classes. Most often this is done by creating a file
* {@code MBeanDescriptions.properties} in the same directory as {@code
* ConfigurationMBean.java}. Make sure that this file is copied into the
* same place as the compiled classes; in typical build environments that
* will be true by default.</p>
*
* <p>The file {@code com/example/MBeanDescriptions.properties} might
* look like this:</p>
*
* <pre>
* # Description of the MBean
* ConfigurationMBean.mbean = Configuration manager
*
* # Description of the Name attribute
* ConfigurationMBean.attribute.Name = The name of the configuration
*
* # Description of the save operation
* ConfigurationMBean.operation.save = Save the configuration to a file
*
* # Description of the parameter to the save operation.
* # Parameter names from the original Java source are not available,
* # so the default names are p1, p2, etc. If the names were available,
* # this would be ConfigurationMBean.operation.save.fileName
* ConfigurationMBean.operation.save.p1 = The name of the file
*
* # Description of the constructor. The default name of a constructor is
* # its fully-qualified class name.
* ConfigurationMBean.constructor.com.example.Configuration = <!--
* -->Constructor with name of default file
* # Description of the constructor parameter.
* ConfigurationMBean.constructor.com.example.Configuration.p1 = <!--
* -->Name of the default file
* </pre>
*
* <p>Starting with this file, you could create descriptions for the French
* locale by creating {@code com/example/MBeanDescriptions_fr.properties}.
* The keys in this file are the same as before but the text has been
* translated:
*
* <pre>
* ConfigurationMBean.mbean = Gestionnaire de configuration
*
* ConfigurationMBean.attribute.Name = Le nom de la configuration
*
* ConfigurationMBean.operation.save = Sauvegarder la configuration <!--
* -->dans un fichier
*
* ConfigurationMBean.operation.save.p1 = Le nom du fichier
*
* ConfigurationMBean.constructor.com.example.Configuration = <!--
* -->Constructeur avec nom du fichier par d&eacute;faut
* ConfigurationMBean.constructor.com.example.Configuration.p1 = <!--
* -->Nom du fichier par d&eacute;faut
* </pre>
*
* <p>The descriptions in {@code MBeanDescriptions.properties} and
* {@code MBeanDescriptions_fr.properties} will only be consulted if
* {@code localizeDescriptions} is called, perhaps because the
* MBean Server has been wrapped by {@link
* ClientContext#newLocalizeMBeanInfoForwarder} or because the
* connector server has been created with the {@link
* javax.management.remote.JMXConnectorServer#LOCALIZE_MBEAN_INFO_FORWARDER
* LOCALIZE_MBEAN_INFO_FORWARDER} option. If you want descriptions
* even when there is no localization step, then you should consider
* using {@link Description &#64;Description} annotations. Annotations
* provide descriptions by default but are overridden if {@code
* localizeDescriptions} is called.</p>
*
* @param locale the target locale for descriptions. Cannot be null.
*
* @param loader the {@code ClassLoader} to use for looking up resource
* bundles.
*
* @return an {@code MBeanInfo} with descriptions appropriately localized.
*
* @throws NullPointerException if {@code locale} is null.
*/
public MBeanInfo localizeDescriptions(Locale locale, ClassLoader loader) {
if (locale == null)
throw new NullPointerException("locale");
Descriptor d = getDescriptor();
String mbiLocaleString = (String) d.getFieldValue(JMX.LOCALE_FIELD);
if (locale.toString().equals(mbiLocaleString))
return this;
return new Rewriter(this, locale, loader).getMBeanInfo();
}
private static class Rewriter {
private final MBeanInfo mbi;
private final ClassLoader loader;
private final Locale locale;
private final String packageName;
private final String simpleClassNamePlusDot;
private ResourceBundle defaultBundle;
private boolean defaultBundleLoaded;
// ResourceBundle.getBundle throws NullPointerException
// if the loader is null, even though that is perfectly
// valid and means the bootstrap loader. So we work
// around with a ClassLoader that is equivalent to the
// bootstrap loader but is not null.
private static final ClassLoader bootstrapLoader =
new ClassLoader(null) {};
Rewriter(MBeanInfo mbi, Locale locale, ClassLoader loader) {
this.mbi = mbi;
this.locale = locale;
if (loader == null)
loader = bootstrapLoader;
this.loader = loader;
String intfName = (String)
mbi.getDescriptor().getFieldValue("interfaceClassName");
if (intfName == null)
intfName = mbi.getClassName();
int lastDot = intfName.lastIndexOf('.');
this.packageName = intfName.substring(0, lastDot + 1);
this.simpleClassNamePlusDot = intfName.substring(lastDot + 1) + ".";
// Inner classes show up as Outer$Inner so won't match the dot.
// When there is no dot, lastDot is -1,
// packageName is empty, and simpleClassNamePlusDot is intfName.
}
MBeanInfo getMBeanInfo() {
MBeanAttributeInfo[] mbais =
rewrite(mbi.getAttributes(), "attribute.");
MBeanOperationInfo[] mbois =
rewrite(mbi.getOperations(), "operation.");
MBeanConstructorInfo[] mbcis =
rewrite(mbi.getConstructors(), "constructor.");
MBeanNotificationInfo[] mbnis =
rewrite(mbi.getNotifications(), "notification.");
Descriptor d = mbi.getDescriptor();
d = changeLocale(d);
String description = getDescription(d, "mbean", "");
if (description == null)
description = mbi.getDescription();
return new MBeanInfo(
mbi.getClassName(), description,
mbais, mbcis, mbois, mbnis, d);
}
private Descriptor changeLocale(Descriptor d) {
if (d.getFieldValue(JMX.LOCALE_FIELD) != null) {
Map<String, Object> map = new HashMap<String, Object>();
for (String field : d.getFieldNames())
map.put(field, d.getFieldValue(field));
map.remove(JMX.LOCALE_FIELD);
d = new ImmutableDescriptor(map);
}
return ImmutableDescriptor.union(
d, new ImmutableDescriptor(JMX.LOCALE_FIELD + "=" + locale));
}
private String getDescription(
Descriptor d, String defaultPrefix, String defaultSuffix) {
ResourceBundle bundle = bundleFromDescriptor(d);
if (bundle == null)
return null;
String key =
(String) d.getFieldValue(JMX.DESCRIPTION_RESOURCE_KEY_FIELD);
if (key == null)
key = simpleClassNamePlusDot + defaultPrefix + defaultSuffix;
return descriptionFromResource(bundle, key);
}
private <T extends MBeanFeatureInfo> T[] rewrite(
T[] features, String resourcePrefix) {
for (int i = 0; i < features.length; i++) {
T feature = features[i];
Descriptor d = feature.getDescriptor();
String description =
getDescription(d, resourcePrefix, feature.getName());
if (description != null &&
!description.equals(feature.getDescription())) {
features[i] = setDescription(feature, description);
}
}
return features;
}
private <T extends MBeanFeatureInfo> T setDescription(
T feature, String description) {
Object newf;
String name = feature.getName();
Descriptor d = feature.getDescriptor();
if (feature instanceof MBeanAttributeInfo) {
MBeanAttributeInfo mbai = (MBeanAttributeInfo) feature;
newf = new MBeanAttributeInfo(
name, mbai.getType(), description,
mbai.isReadable(), mbai.isWritable(), mbai.isIs(),
d);
} else if (feature instanceof MBeanOperationInfo) {
MBeanOperationInfo mboi = (MBeanOperationInfo) feature;
MBeanParameterInfo[] sig = rewrite(
mboi.getSignature(), "operation." + name + ".");
newf = new MBeanOperationInfo(
name, description, sig,
mboi.getReturnType(), mboi.getImpact(), d);
} else if (feature instanceof MBeanConstructorInfo) {
MBeanConstructorInfo mbci = (MBeanConstructorInfo) feature;
MBeanParameterInfo[] sig = rewrite(
mbci.getSignature(), "constructor." + name + ".");
newf = new MBeanConstructorInfo(
name, description, sig, d);
} else if (feature instanceof MBeanNotificationInfo) {
MBeanNotificationInfo mbni = (MBeanNotificationInfo) feature;
newf = new MBeanNotificationInfo(
mbni.getNotifTypes(), name, description, d);
} else if (feature instanceof MBeanParameterInfo) {
MBeanParameterInfo mbpi = (MBeanParameterInfo) feature;
newf = new MBeanParameterInfo(
name, mbpi.getType(), description, d);
} else {
logger().log(Level.FINE, "Unknown feature type: " +
feature.getClass());
newf = feature;
}
return Util.<T>cast(newf);
}
private ResourceBundle bundleFromDescriptor(Descriptor d) {
String bundleName = (String) d.getFieldValue(
JMX.DESCRIPTION_RESOURCE_BUNDLE_BASE_NAME_FIELD);
if (bundleName != null)
return getBundle(bundleName);
if (defaultBundleLoaded)
return defaultBundle;
bundleName = packageName + "MBeanDescriptions";
defaultBundle = getBundle(bundleName);
defaultBundleLoaded = true;
return defaultBundle;
}
private String descriptionFromResource(
ResourceBundle bundle, String key) {
try {
return bundle.getString(key);
} catch (MissingResourceException e) {
logger().log(Level.FINEST, "No resource for " + key, e);
} catch (Exception e) {
logger().log(Level.FINE, "Bad resource for " + key, e);
}
return null;
}
private ResourceBundle getBundle(String name) {
try {
return ResourceBundle.getBundle(name, locale, loader);
} catch (Exception e) {
logger().log(Level.FINE,
"Could not load ResourceBundle " + name, e);
return null;
}
}
private Logger logger() {
return Logger.getLogger("javax.management.locale");
}
}
}
......@@ -27,15 +27,43 @@ package javax.management;
/**
* Represents a notification emitted by the MBean server through the MBeanServerDelegate MBean.
* Represents a notification emitted by the MBean Server through the MBeanServerDelegate MBean.
* The MBean Server emits the following types of notifications: MBean registration, MBean
* de-registration.
* unregistration.
* <P>
* To receive to MBeanServerNotifications, you need to be declared as listener to
* the {@link javax.management.MBeanServerDelegate javax.management.MBeanServerDelegate} MBean
* that represents the MBeanServer. The ObjectName of the MBeanServerDelegate is:
* To receive MBeanServerNotifications, you need to register a listener with
* the {@link MBeanServerDelegate MBeanServerDelegate} MBean
* that represents the MBeanServer. The ObjectName of the MBeanServerDelegate is
* {@link MBeanServerDelegate#DELEGATE_NAME}, which is
* <CODE>JMImplementation:type=MBeanServerDelegate</CODE>.
*
* <p>The following code prints a message every time an MBean is registered
* or unregistered in the MBean Server {@code mbeanServer}:</p>
*
* <pre>
* private static final NotificationListener printListener = new NotificationListener() {
* public void handleNotification(Notification n, Object handback) {
* if (!(n instanceof MBeanServerNotification)) {
* System.out.println("Ignored notification of class " + n.getClass().getName());
* return;
* }
* MBeanServerNotification mbsn = (MBeanServerNotification) n;
* String what;
* if (n.getType().equals(MBeanServerNotification.REGISTRATION_NOTIFICATION))
* what = "MBean registered";
* else if (n.getType().equals(MBeanServerNotification.UNREGISTRATION_NOTIFICATION))
* what = "MBean unregistered";
* else
* what = "Unknown type " + n.getType();
* System.out.println("Received MBean Server notification: " + what + ": " +
* mbsn.getMBeanName());
* };
*
* ...
* mbeanServer.addNotificationListener(
* MBeanServerDelegate.DELEGATE_NAME, printListener, null, null);
* </pre>
*
* @since 1.5
*/
public class MBeanServerNotification extends Notification {
......
......@@ -54,7 +54,7 @@ import com.sun.jmx.mbeanserver.GetPropertyAction;
* @since 1.5
*/
@SuppressWarnings("serial") // serialVersionUID is not constant
public class Notification extends EventObject {
public class Notification extends EventObject implements Cloneable {
// Serialization compatibility stuff:
// Two serial forms are supported in this class. The selected form depends
......@@ -243,6 +243,26 @@ public class Notification extends EventObject {
this.message = message ;
}
/**
* <p>Creates and returns a copy of this object. The copy is created as
* described for {@link Object#clone()}. This means, first, that the
* class of the object will be the same as the class of this object, and,
* second, that the copy is a "shallow copy". Fields of this notification
* are not themselves copied. In particular, the {@linkplain
* #getUserData user data} of the copy is the same object as the
* original.</p>
*
* @return a copy of this object.
*/
@Override
public Object clone() {
try {
return super.clone();
} catch (CloneNotSupportedException e) {
throw new AssertionError(e);
}
}
/**
* Sets the source.
*
......@@ -285,8 +305,10 @@ public class Notification extends EventObject {
/**
* Get the notification type.
*
* @return The notification type. It's a string expressed in a dot notation similar
* to Java properties. An example of a notification type is network.alarm.router .
* @return The notification type. It's a string expressed in a dot notation
* similar to Java properties. It is recommended that the notification type
* should follow the reverse-domain-name convention used by Java package
* names. An example of a notification type is com.example.alarm.router.
*/
public String getType() {
return type ;
......@@ -317,14 +339,25 @@ public class Notification extends EventObject {
/**
* Get the notification message.
*
* @return The message string of this notification object. It contains in a string,
* which could be the explanation of the notification for displaying to a user
* @return The message string of this notification object.
*
* @see #setMessage
*/
public String getMessage() {
return message ;
}
/**
* Set the notification message.
*
* @param message the new notification message.
*
* @see #getMessage
*/
public void setMessage(String message) {
this.message = message;
}
/**
* Get the user data.
*
......@@ -355,6 +388,7 @@ public class Notification extends EventObject {
*
* @return A String representation of this notification.
*/
@Override
public String toString() {
return super.toString()+"[type="+type+"][message="+message+"]";
}
......
......@@ -29,7 +29,6 @@ import com.sun.jmx.event.DaemonThreadFactory;
import com.sun.jmx.event.LeaseRenewer;
import com.sun.jmx.event.ReceiverBuffer;
import com.sun.jmx.event.RepeatedSingletonJob;
import com.sun.jmx.namespace.JMXNamespaceUtils;
import com.sun.jmx.mbeanserver.PerThreadGroupPool;
import com.sun.jmx.remote.util.ClassLogger;
......@@ -58,7 +57,6 @@ import javax.management.NotificationBroadcasterSupport;
import javax.management.NotificationFilter;
import javax.management.NotificationListener;
import javax.management.ObjectName;
import javax.management.remote.JMXConnector;
import javax.management.remote.NotificationResult;
import javax.management.remote.TargetedNotification;
......@@ -129,11 +127,12 @@ public class EventClient implements EventConsumer, NotificationManager {
public static final String NOTIFS_LOST = "jmx.event.service.notifs.lost";
/**
* The default lease time, {@value}, in milliseconds.
* The default lease time that EventClient instances will request, in
* milliseconds. This value is {@value}.
*
* @see EventClientDelegateMBean#lease
*/
public static final long DEFAULT_LEASE_TIMEOUT = 300000;
public static final long DEFAULT_REQUESTED_LEASE_TIME = 300000;
/**
* <p>Constructs a default {@code EventClient} object.</p>
......@@ -173,7 +172,7 @@ public class EventClient implements EventConsumer, NotificationManager {
*/
public EventClient(EventClientDelegateMBean delegate)
throws IOException {
this(delegate, null, null, null, DEFAULT_LEASE_TIMEOUT);
this(delegate, null, null, null, DEFAULT_REQUESTED_LEASE_TIME);
}
/**
......@@ -196,7 +195,7 @@ public class EventClient implements EventConsumer, NotificationManager {
* If {@code null}, a default scheduler will be used.
* @param requestedLeaseTime The lease time used to keep this client alive
* in the {@link EventClientDelegateMBean}. A value of zero is equivalent
* to the {@linkplain #DEFAULT_LEASE_TIMEOUT default value}.
* to the {@linkplain #DEFAULT_REQUESTED_LEASE_TIME default value}.
*
* @throws IllegalArgumentException If {@code delegate} is null.
* @throws IOException If an I/O error occurs when communicating with the
......@@ -213,7 +212,7 @@ public class EventClient implements EventConsumer, NotificationManager {
}
if (requestedLeaseTime == 0)
requestedLeaseTime = DEFAULT_LEASE_TIMEOUT;
requestedLeaseTime = DEFAULT_REQUESTED_LEASE_TIME;
else if (requestedLeaseTime < 0) {
throw new IllegalArgumentException(
"Negative lease time: " + requestedLeaseTime);
......@@ -269,7 +268,13 @@ public class EventClient implements EventConsumer, NotificationManager {
new ScheduledThreadPoolExecutor(20, daemonThreadFactory);
executor.setKeepAliveTime(1, TimeUnit.SECONDS);
executor.allowCoreThreadTimeOut(true);
executor.setRemoveOnCancelPolicy(true);
if (setRemoveOnCancelPolicy != null) {
try {
setRemoveOnCancelPolicy.invoke(executor, true);
} catch (Exception e) {
logger.trace("setRemoveOnCancelPolicy", e);
}
}
// By default, a ScheduledThreadPoolExecutor will keep jobs
// in its queue even after they have been cancelled. They
// will only be removed when their scheduled time arrives.
......@@ -277,12 +282,25 @@ public class EventClient implements EventConsumer, NotificationManager {
// this EventClient, this can lead to a moderately large number
// of objects remaining referenced until the renewal time
// arrives. Hence the above call, which removes the job from
// the queue as soon as it is cancelled.
// the queue as soon as it is cancelled. Since the call is
// new with JDK 7, we invoke it via reflection to make it
// easier to use this code on JDK 6.
return executor;
}
};
return leaseRenewerThreadPool.getThreadPoolExecutor(create);
}
private static final Method setRemoveOnCancelPolicy;
static {
Method m;
try {
m = ScheduledThreadPoolExecutor.class.getMethod(
"setRemoveOnCancelPolicy", boolean.class);
} catch (Exception e) {
m = null;
}
setRemoveOnCancelPolicy = m;
}
/**
......@@ -1042,7 +1060,7 @@ public class EventClient implements EventConsumer, NotificationManager {
final public EventClient call() throws Exception {
EventClientDelegateMBean ecd = EventClientDelegate.getProxy(conn);
return new EventClient(ecd, eventRelay, null, null,
DEFAULT_LEASE_TIMEOUT);
DEFAULT_REQUESTED_LEASE_TIME);
}
};
......@@ -1080,24 +1098,6 @@ public class EventClient implements EventConsumer, NotificationManager {
return clientId;
}
/**
* Returns a JMX Connector that will use an {@link EventClient}
* to subscribe for notifications. If the server doesn't have
* an {@link EventClientDelegateMBean}, then the connector will
* use the legacy notification mechanism instead.
*
* @param wrapped The underlying JMX Connector wrapped by the returned
* connector.
*
* @return A JMX Connector that will uses an {@link EventClient}, if
* available.
*
* @see EventClient#getEventClientConnection(MBeanServerConnection)
*/
public static JMXConnector withEventClient(final JMXConnector wrapped) {
return JMXNamespaceUtils.withEventClient(wrapped);
}
private static final PerThreadGroupPool<ScheduledThreadPoolExecutor>
leaseRenewerThreadPool = PerThreadGroupPool.make();
}
......@@ -149,6 +149,7 @@ public class EventClientDelegate implements EventClientDelegateMBean {
// of a setMBeanServer on some other forwarder later in the chain.
private static class Forwarder extends SingleMBeanForwarder {
private MBeanServer loopMBS;
private static class UnsupportedInvocationHandler
implements InvocationHandler {
......@@ -173,7 +174,11 @@ public class EventClientDelegate implements EventClientDelegateMBean {
private volatile boolean madeECD;
Forwarder() {
super(OBJECT_NAME, makeUnsupportedECD());
super(OBJECT_NAME, makeUnsupportedECD(), true);
}
synchronized void setLoopMBS(MBeanServer loopMBS) {
this.loopMBS = loopMBS;
}
@Override
......@@ -186,7 +191,7 @@ public class EventClientDelegate implements EventClientDelegateMBean {
AccessController.doPrivileged(
new PrivilegedAction<EventClientDelegate>() {
public EventClientDelegate run() {
return getEventClientDelegate(Forwarder.this);
return getEventClientDelegate(loopMBS);
}
});
DynamicMBean mbean = new StandardMBean(
......@@ -208,11 +213,46 @@ public class EventClientDelegate implements EventClientDelegateMBean {
* that are targeted for that MBean and handles them itself. All other
* requests are forwarded to the next element in the forwarder chain.</p>
*
* @param nextMBS the next {@code MBeanServer} in the chain of forwarders,
* which might be another {@code MBeanServerForwarder} or a plain {@code
* MBeanServer}. This is the object to which {@code MBeanServer} requests
* that do not concern the {@code EventClientDelegateMBean} are sent.
* It will be the value of {@link MBeanServerForwarder#getMBeanServer()
* getMBeanServer()} on the returned object, and can be changed with {@link
* MBeanServerForwarder#setMBeanServer setMBeanServer}. It can be null but
* must be set to a non-null value before any {@code MBeanServer} requests
* arrive.
*
* @param loopMBS the {@code MBeanServer} to which requests from the
* {@code EventClientDelegateMBean} should be sent. For example,
* when you invoke the {@link EventClientDelegateMBean#addListener
* addListener} operation on the {@code EventClientDelegateMBean}, it will
* result in a call to {@link
* MBeanServer#addNotificationListener(ObjectName, NotificationListener,
* NotificationFilter, Object) addNotificationListener} on this object.
* If this parameter is null, then these requests will be sent to the
* newly-created {@code MBeanServerForwarder}. Usually the parameter will
* either be null or will be the result of {@link
* javax.management.remote.JMXConnectorServer#getSystemMBeanServerForwarder()
* getSystemMBeanServerForwarder()} for the connector server in which
* this forwarder will be installed.
*
* @return a new {@code MBeanServerForwarder} that simulates the existence
* of an {@code EventClientDelegateMBean}.
*
* @see javax.management.remote.JMXConnectorServer#installStandardForwarders
*/
public static MBeanServerForwarder newForwarder() {
return new Forwarder();
public static MBeanServerForwarder newForwarder(
MBeanServer nextMBS, MBeanServer loopMBS) {
Forwarder mbsf = new Forwarder();
// We must setLoopMBS before setMBeanServer, because when we
// setMBeanServer that will call getEventClientDelegate(loopMBS).
if (loopMBS == null)
loopMBS = mbsf;
mbsf.setLoopMBS(loopMBS);
if (nextMBS != null)
mbsf.setMBeanServer(nextMBS);
return mbsf;
}
/**
......@@ -437,10 +477,9 @@ public class EventClientDelegate implements EventClientDelegateMBean {
// private classes
// ------------------------------------
private class ClientInfo {
String clientId;
EventBuffer buffer;
NotificationListener clientListener;
Map<Integer, AddedListener> listenerInfoMap =
final String clientId;
final NotificationListener clientListener;
final Map<Integer, AddedListener> listenerInfoMap =
new HashMap<Integer, AddedListener>();
ClientInfo(String clientId, EventForwarder forwarder) {
......@@ -703,7 +742,8 @@ public class EventClientDelegate implements EventClientDelegateMBean {
clientInfo = clientInfoMap.get(clientId);
if (clientInfo == null) {
throw new EventClientNotFoundException("The client is not found.");
throw new EventClientNotFoundException(
"Client not found (id " + clientId + ")");
}
return clientInfo;
......
......@@ -51,7 +51,8 @@ import javax.management.remote.NotificationResult;
* and the MBean Server, that will intercept accesses to the Event Client
* Delegate MBean and treat them as the real MBean would. This forwarder is
* inserted by default with the standard RMI Connector Server, and can also
* be created explicitly using {@link EventClientDelegate#newForwarder()}.
* be created explicitly using {@link EventClientDelegate#newForwarder
* EventClientDelegate.newForwarder}.
*
* <li><p>A variant on the above is to replace the MBean Server that is
* used locally with a forwarder as described above. Since
......@@ -61,9 +62,7 @@ import javax.management.remote.NotificationResult;
*
* <pre>
* MBeanServer mbs = ManagementFactory.getPlatformMBeanServer(); // or whatever
* MBeanServerForwarder mbsf = EventClientDelegate.newForwarder();
* mbsf.setMBeanServer(mbs);
* mbs = mbsf;
* mbs = EventClientDelegate.newForwarder(mbs, null);
* // now use mbs just as you did before, but it will have an EventClientDelegate
* </pre>
*
......
......@@ -27,7 +27,6 @@ package javax.management.event;
import java.io.IOException;
import java.util.concurrent.Executors; // for javadoc
import java.util.concurrent.ScheduledFuture;
/**
* This interface is used to specify a way to receive
......
......@@ -83,8 +83,8 @@
* javax.management.event.EventClientDelegateMBean EventClientDelegateMBean}
* must be registered in the MBean Server, or the connector server must
* be configured to simulate the existence of this MBean, for example
* using {@link javax.management.event.EventClientDelegate#newForwarder()
* EventClientDelegate.newForwarder()}. The standard RMI connector is so
* using {@link javax.management.event.EventClientDelegate#newForwarder
* EventClientDelegate.newForwarder}. The standard RMI connector is so
* configured by default. The {@code EventClientDelegateMBean} documentation
* has further details.</p>
*
......
......@@ -26,21 +26,19 @@
package javax.management.namespace;
import com.sun.jmx.defaults.JmxProperties;
import com.sun.jmx.namespace.JMXNamespaceUtils;
import com.sun.jmx.namespace.ObjectNameRouter;
import com.sun.jmx.namespace.serial.RewritingProcessor;
import com.sun.jmx.namespace.RoutingConnectionProxy;
import com.sun.jmx.namespace.RoutingServerProxy;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.management.InstanceNotFoundException;
import javax.management.MBeanServer;
import javax.management.MBeanServerConnection;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;
import javax.management.remote.JMXConnector;
/**
* Static constants and utility methods to help work with
......@@ -68,23 +66,6 @@ public class JMXNamespaces {
NAMESPACE_SEPARATOR.length();
/**
* Returns a connector connected to a sub name space exposed through
* the parent connector.
* @param parent the parent connector.
* @param namespace the {@linkplain javax.management.namespace name space}
* to which the returned connector is
* connected.
* @return A connector connected to a sub name space exposed through
* the parent connector.
**/
public static JMXConnector narrowToNamespace(final JMXConnector parent,
final String namespace)
throws IOException {
return JMXNamespaceUtils.cd(parent,namespace,true);
}
/**
* Creates a new {@code MBeanServerConnection} proxy on a
* {@linkplain javax.management.namespace sub name space}
......@@ -96,15 +77,18 @@ public class JMXNamespaces {
* name space} in which to narrow.
* @return A new {@code MBeanServerConnection} proxy that shows the content
* of that name space.
* @throws IllegalArgumentException if the name space does not exist, or
* if a proxy for that name space cannot be created.
* @throws IllegalArgumentException if either argument is null,
* or the name space does not exist, or if a proxy for that name space
* cannot be created. The {@linkplain Throwable#getCause() cause} of
* this exception will be an {@link InstanceNotFoundException} if and only
* if the name space is found not to exist.
*/
public static MBeanServerConnection narrowToNamespace(
MBeanServerConnection parent,
String namespace) {
if (LOG.isLoggable(Level.FINER))
LOG.finer("Making MBeanServerConnection for: " +namespace);
return RoutingConnectionProxy.cd(parent,namespace);
return RoutingConnectionProxy.cd(parent, namespace, true);
}
/**
......@@ -120,13 +104,15 @@ public class JMXNamespaces {
* of that name space.
* @throws IllegalArgumentException if either argument is null,
* or the name space does not exist, or if a proxy for that name space
* cannot be created.
* cannot be created. The {@linkplain Throwable#getCause() cause} of
* this exception will be an {@link InstanceNotFoundException} if and only
* if the name space is found not to exist.
*/
public static MBeanServer narrowToNamespace(MBeanServer parent,
String namespace) {
if (LOG.isLoggable(Level.FINER))
LOG.finer("Making NamespaceServerProxy for: " +namespace);
return RoutingServerProxy.cd(parent,namespace);
LOG.finer("Making MBeanServer for: " +namespace);
return RoutingServerProxy.cd(parent, namespace, true);
}
/**
......@@ -266,7 +252,7 @@ public class JMXNamespaces {
ObjectNameRouter.normalizeNamespacePath(namespace,false,
true,false);
try {
// We could use Util.newObjectName here - but throwing an
// We could use ObjectName.valueOf here - but throwing an
// IllegalArgumentException that contains just the supplied
// namespace instead of the whole ObjectName seems preferable.
return ObjectName.getInstance(sourcePath+
......
......@@ -27,10 +27,10 @@ package javax.management.namespace;
import com.sun.jmx.defaults.JmxProperties;
import com.sun.jmx.mbeanserver.Util;
import com.sun.jmx.namespace.JMXNamespaceUtils;
import com.sun.jmx.remote.util.EnvHelp;
import java.io.IOException;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicLong;
......@@ -39,6 +39,7 @@ import java.util.logging.Logger;
import javax.management.AttributeChangeNotification;
import javax.management.ClientContext;
import javax.management.InstanceNotFoundException;
import javax.management.ListenerNotFoundException;
import javax.management.MBeanNotificationInfo;
......@@ -220,17 +221,26 @@ public class JMXRemoteNamespace
initParentOnce(this);
// URL must not be null.
this.jmxURL = JMXNamespaceUtils.checkNonNull(sourceURL,"url");
if (sourceURL == null)
throw new IllegalArgumentException("Null URL");
this.jmxURL = sourceURL;
this.broadcaster =
new NotificationBroadcasterSupport(connectNotification);
// handles options
this.optionsMap = JMXNamespaceUtils.unmodifiableMap(optionsMap);
this.optionsMap = unmodifiableMap(optionsMap);
// handles (dis)connection events
this.listener = new ConnectionListener();
}
// returns un unmodifiable view of a map.
private static <K,V> Map<K,V> unmodifiableMap(Map<K,V> aMap) {
if (aMap == null || aMap.isEmpty())
return Collections.emptyMap();
return Collections.unmodifiableMap(aMap);
}
/**
* Returns the {@code JMXServiceURL} that is (or will be) used to
* connect to the remote name space. <p>
......@@ -483,106 +493,171 @@ public class JMXRemoteNamespace
}
}
JMXConnector connect(JMXServiceURL url, Map<String,?> env)
private JMXConnector connect(JMXServiceURL url, Map<String,?> env)
throws IOException {
final JMXConnector c = newJMXConnector(jmxURL, env);
final JMXConnector c = newJMXConnector(url, env);
c.connect(env);
return c;
}
/**
* Creates a new JMXConnector with the specified {@code url} and
* {@code env} options map.
* <p>
* This method first calls {@link JMXConnectorFactory#newJMXConnector
* JMXConnectorFactory.newJMXConnector(jmxURL, env)} to obtain a new
* JMX connector, and returns that.
* </p>
* <p>
* A subclass of {@link JMXRemoteNamespace} can provide an implementation
* that connects to a sub namespace of the remote server by subclassing
* this class in the following way:
* <p>Creates a new JMXConnector with the specified {@code url} and
* {@code env} options map. The default implementation of this method
* returns {@link JMXConnectorFactory#newJMXConnector
* JMXConnectorFactory.newJMXConnector(jmxURL, env)}. Subclasses can
* override this method to customize behavior.</p>
*
* @param url The JMXServiceURL of the remote server.
* @param optionsMap An options map that will be passed to the
* {@link JMXConnectorFactory} when {@linkplain
* JMXConnectorFactory#newJMXConnector creating} the
* {@link JMXConnector} that can connect to the remote source
* MBean Server.
* @return A JMXConnector to use to connect to the remote server
* @throws IOException if the connector could not be created.
* @see JMXConnectorFactory#newJMXConnector(javax.management.remote.JMXServiceURL, java.util.Map)
* @see #JMXRemoteNamespace
*/
protected JMXConnector newJMXConnector(JMXServiceURL url,
Map<String,?> optionsMap) throws IOException {
return JMXConnectorFactory.newJMXConnector(jmxURL, optionsMap);
}
/**
* <p>Called when a new connection is established using {@link #connect}
* so that subclasses can customize the connection. The default
* implementation of this method effectively does the following:</p>
*
* <pre>
* MBeanServerConnection mbsc = {@link JMXConnector#getMBeanServerConnection()
* jmxc.getMBeanServerConnection()};
* try {
* return {@link ClientContext#withDynamicContext
* ClientContext.withDynamicContext(mbsc)};
* } catch (IllegalArgumentException e) {
* return mbsc;
* }
* </pre>
*
* <p>In other words, it arranges for the client context to be forwarded
* to the remote MBean Server if the remote MBean Server supports contexts;
* otherwise it ignores the client context.</p>
*
* <h4>Example: connecting to a remote namespace</h4>
*
* <p>A subclass that wanted to narrow into a namespace of
* the remote MBeanServer might look like this:</p>
*
* <pre>
* class JMXRemoteSubNamespace extends JMXRemoteNamespace {
* private final String subnamespace;
* JMXRemoteSubNamespace(JMXServiceURL url,
* Map{@code <String,?>} env, String subnamespace) {
* super(url,options);
* private final String subnamespace;
*
* JMXRemoteSubNamespace(
* JMXServiceURL url, Map{@code <String, ?>} env, String subnamespace) {
* super(url, env);
* this.subnamespace = subnamespace;
* }
* protected JMXConnector newJMXConnector(JMXServiceURL url,
* Map<String,?> env) throws IOException {
* final JMXConnector inner = super.newJMXConnector(url,env);
* return {@link JMXNamespaces#narrowToNamespace(JMXConnector,String)
* JMXNamespaces.narrowToNamespace(inner,subnamespace)};
* }
* }
*
* {@code @Override}
* protected MBeanServerConnection getMBeanServerConnection(
* JMXConnector jmxc) throws IOException {
* MBeanServerConnection mbsc = super.getMBeanServerConnection(jmxc);
* return {@link JMXNamespaces#narrowToNamespace(MBeanServerConnection,String)
* JMXNamespaces.narrowToNamespace(mbsc, subnamespace)};
* }
* }
* </pre>
* </p>
* <p>
* Some connectors, like the JMXMP connector server defined by the
* version 1.2 of the JMX API may not have been upgraded to use the
* new {@linkplain javax.management.event Event Service} defined in this
* version of the JMX API.
* <p>
* In that case, and if the remote server to which this JMXRemoteNamespace
* connects also contains namespaces, it may be necessary to configure
* explicitly an {@linkplain
* javax.management.event.EventClientDelegate#newForwarder()
* Event Client Forwarder} on the remote server side, and to force the use
* of an {@link EventClient} on this client side.
* <br>
* A subclass of {@link JMXRemoteNamespace} can provide an implementation
* of {@code newJMXConnector} that will force notification subscriptions
* to flow through an {@link EventClient} over a legacy protocol by
* overriding this method in the following way:
* </p>
*
* <h4>Example: using the Event Service for notifications</h4>
*
* <p>Some connectors may have been designed to work with an earlier
* version of the JMX API, and may not have been upgraded to use
* the {@linkplain javax.management.event Event Service} defined in
* this version of the JMX API. In that case, and if the remote
* server to which this JMXRemoteNamespace connects also contains
* namespaces, it may be necessary to configure explicitly an {@linkplain
* javax.management.event.EventClientDelegate#newForwarder Event Client
* Forwarder} on the remote server side, and to force the use of an {@link
* EventClient} on this client side.</p>
*
* <p>A subclass of {@link JMXRemoteNamespace} can provide an
* implementation of {@code getMBeanServerConnection} that will force
* notification subscriptions to flow through an {@link EventClient} over
* a legacy protocol. It can do so by overriding this method in the
* following way:</p>
*
* <pre>
* class JMXRemoteEventClientNamespace extends JMXRemoteNamespace {
* JMXRemoteSubNamespaceConnector(JMXServiceURL url,
* Map<String,?> env) {
* super(url,options);
* }
* protected JMXConnector newJMXConnector(JMXServiceURL url,
* Map<String,?> env) throws IOException {
* final JMXConnector inner = super.newJMXConnector(url,env);
* return {@link EventClient#withEventClient(
* JMXConnector) EventClient.withEventClient(inner)};
* }
* JMXRemoteEventClientNamespace(JMXServiceURL url, {@code Map<String,?>} env) {
* super(url, env);
* }
*
* {@code @Override}
* protected MBeanServerConnection getMBeanServerConnection(JMXConnector jmxc)
* throws IOException {
* MBeanServerConnection mbsc = super.getMBeanServerConnection(jmxc);
* return EventClient.getEventClientConnection(mbsc);
* }
* }
* </pre>
*
* <p>
* Note that the remote server also needs to provide an {@link
* javax.management.event.EventClientDelegateMBean}: only configuring
* the client side (this object) is not enough.<br>
* In summary, this technique should be used if the remote server
* javax.management.event.EventClientDelegateMBean}: configuring only
* the client side (this object) is not enough.</p>
*
* <p>In summary, this technique should be used if the remote server
* supports JMX namespaces, but uses a JMX Connector Server whose
* implementation does not transparently use the new Event Service
* (as would be the case with the JMXMPConnectorServer implementation
* from the reference implementation of the JMX Remote API 1.0
* specification).
* </p>
* @param url The JMXServiceURL of the remote server.
* @param optionsMap An unmodifiable options map that will be passed to the
* {@link JMXConnectorFactory} when {@linkplain
* JMXConnectorFactory#newJMXConnector creating} the
* {@link JMXConnector} that can connect to the remote source
* MBean Server.
* @return An unconnected JMXConnector to use to connect to the remote
* server
* @throws java.io.IOException if the connector could not be created.
* @see JMXConnectorFactory#newJMXConnector(javax.management.remote.JMXServiceURL, java.util.Map)
* @see #JMXRemoteNamespace
* specification).</p>
*
* @param jmxc the newly-created {@code JMXConnector}.
*
* @return an {@code MBeanServerConnection} connected to the remote
* MBeanServer.
*
* @throws IOException if the connection cannot be made. If this method
* throws {@code IOException} then the calling {@link #connect()} method
* will also fail with an {@code IOException}.
*
* @see #connect
*/
protected JMXConnector newJMXConnector(JMXServiceURL url,
Map<String,?> optionsMap) throws IOException {
final JMXConnector c =
JMXConnectorFactory.newJMXConnector(jmxURL, optionsMap);
// TODO: uncomment this when contexts are added
// return ClientContext.withDynamicContext(c);
return c;
protected MBeanServerConnection getMBeanServerConnection(JMXConnector jmxc)
throws IOException {
final MBeanServerConnection mbsc = jmxc.getMBeanServerConnection();
try {
return ClientContext.withDynamicContext(mbsc);
} catch (IllegalArgumentException e) {
LOG.log(Level.FINER, "ClientContext.withDynamicContext", e);
return mbsc;
}
}
/**
* {@inheritDoc}
*
* <p>The sequence of events when this method is called includes,
* effectively, the following code:</p>
*
* <pre>
* JMXServiceURL url = {@link #getJMXServiceURL getJMXServiceURL}();
* JMXConnector jmxc = {@link #newJMXConnector newJMXConnector}(url, env);
* jmxc.connect();
* MBeanServerConnection mbsc = {@link #getMBeanServerConnection(JMXConnector)
* getMBeanServerConnection}(jmxc);
* </pre>
*
* <p>Here, {@code env} is a {@code Map} containing the entries from the
* {@code optionsMap} that was passed to the {@linkplain #JMXRemoteNamespace
* constructor} or to the {@link #newJMXRemoteNamespace newJMXRemoteNamespace}
* factory method.</p>
*
* <p>Subclasses can customize connection behavior by overriding the
* {@code getJMXServiceURL}, {@code newJMXConnector}, or
* {@code getMBeanServerConnection} methods.</p>
*/
public void connect() throws IOException {
LOG.fine("connecting...");
final Map<String,Object> env =
......@@ -590,7 +665,7 @@ public class JMXRemoteNamespace
try {
// XXX: We should probably document this...
// This allows to specify a loader name - which will be
// retrieved from the paret MBeanServer.
// retrieved from the parent MBeanServer.
defaultClassLoader =
EnvHelp.resolveServerClassLoader(env,getMBeanServer());
} catch (InstanceNotFoundException x) {
......@@ -604,7 +679,7 @@ public class JMXRemoteNamespace
final JMXConnector aconn = connect(url,env);
final MBeanServerConnection msc;
try {
msc = aconn.getMBeanServerConnection();
msc = getMBeanServerConnection(aconn);
aconn.addConnectionNotificationListener(listener,null,aconn);
} catch (IOException io) {
close(aconn);
......
......@@ -322,10 +322,12 @@ public class JMXConnectorFactory {
JMXConnectorProvider.class;
final String protocol = serviceURL.getProtocol();
final String providerClassName = "ClientProvider";
final JMXServiceURL providerURL = serviceURL;
JMXConnectorProvider provider =
getProvider(serviceURL, envcopy, providerClassName,
targetInterface, loader);
JMXConnectorProvider provider = getProvider(providerURL, envcopy,
providerClassName,
targetInterface,
loader);
IOException exception = null;
if (provider == null) {
......@@ -336,7 +338,7 @@ public class JMXConnectorFactory {
if (loader != null) {
try {
JMXConnector connection =
getConnectorAsService(loader, serviceURL, envcopy);
getConnectorAsService(loader, providerURL, envcopy);
if (connection != null)
return connection;
} catch (JMXProviderException e) {
......@@ -345,8 +347,7 @@ public class JMXConnectorFactory {
exception = e;
}
}
provider =
getProvider(protocol, PROTOCOL_PROVIDER_DEFAULT_PACKAGE,
provider = getProvider(protocol, PROTOCOL_PROVIDER_DEFAULT_PACKAGE,
JMXConnectorFactory.class.getClassLoader(),
providerClassName, targetInterface);
}
......@@ -448,9 +449,10 @@ public class JMXConnectorFactory {
getProviderIterator(JMXConnectorProvider.class, loader);
JMXConnector connection;
IOException exception = null;
while(providers.hasNext()) {
while (providers.hasNext()) {
JMXConnectorProvider provider = providers.next();
try {
connection = providers.next().newJMXConnector(url, map);
connection = provider.newJMXConnector(url, map);
return connection;
} catch (JMXProviderException e) {
throw e;
......@@ -553,4 +555,5 @@ public class JMXConnectorFactory {
private static String protocol2package(String protocol) {
return protocol.replace('+', '.').replace('-', '_');
}
}
......@@ -132,7 +132,7 @@ public interface JMXConnectorServerMBean {
*
* <p>A connector server may support two chains of forwarders,
* a system chain and a user chain. See {@link
* JMXConnectorServer#setSystemMBeanServerForwarder} for details.</p>
* JMXConnectorServer#getSystemMBeanServerForwarder} for details.</p>
*
* @param mbsf the new <code>MBeanServerForwarder</code>.
*
......@@ -141,7 +141,7 @@ public interface JMXConnectorServerMBean {
* with <code>IllegalArgumentException</code>. This includes the
* case where <code>mbsf</code> is null.
*
* @see JMXConnectorServer#setSystemMBeanServerForwarder
* @see JMXConnectorServer#getSystemMBeanServerForwarder
*/
public void setMBeanServerForwarder(MBeanServerForwarder mbsf);
......
......@@ -383,7 +383,7 @@ public class RMIConnectorServer extends JMXConnectorServer {
try {
if (tracing) logger.trace("start", "setting default class loader");
defaultClassLoader = EnvHelp.resolveServerClassLoader(
attributes, getSystemMBeanServer());
attributes, getSystemMBeanServerForwarder());
} catch (InstanceNotFoundException infc) {
IllegalArgumentException x = new
IllegalArgumentException("ClassLoader not found: "+infc);
......@@ -398,7 +398,7 @@ public class RMIConnectorServer extends JMXConnectorServer {
else
rmiServer = newServer();
rmiServer.setMBeanServer(getSystemMBeanServer());
rmiServer.setMBeanServer(getSystemMBeanServerForwarder());
rmiServer.setDefaultClassLoader(defaultClassLoader);
rmiServer.setRMIConnectorServer(this);
rmiServer.export();
......@@ -592,31 +592,6 @@ public class RMIConnectorServer extends JMXConnectorServer {
return Collections.unmodifiableMap(map);
}
@Override
public synchronized void setMBeanServerForwarder(MBeanServerForwarder mbsf) {
MBeanServer oldSMBS = getSystemMBeanServer();
super.setMBeanServerForwarder(mbsf);
if (oldSMBS != getSystemMBeanServer())
updateMBeanServer();
// If the system chain of MBeanServerForwarders is not empty, then
// there is no need to call rmiServerImpl.setMBeanServer, because
// it is pointing to the head of the system chain and that has not
// changed. (The *end* of the system chain will have been changed
// to point to mbsf.)
}
private void updateMBeanServer() {
if (rmiServerImpl != null)
rmiServerImpl.setMBeanServer(getSystemMBeanServer());
}
@Override
public synchronized void setSystemMBeanServerForwarder(
MBeanServerForwarder mbsf) {
super.setSystemMBeanServerForwarder(mbsf);
updateMBeanServer();
}
/**
* {@inheritDoc}
* @return true, since this connector server does support a system chain
......@@ -631,16 +606,19 @@ public class RMIConnectorServer extends JMXConnectorServer {
here so that they are accessible to other classes in this package
even though they have protected access. */
@Override
protected void connectionOpened(String connectionId, String message,
Object userData) {
super.connectionOpened(connectionId, message, userData);
}
@Override
protected void connectionClosed(String connectionId, String message,
Object userData) {
super.connectionClosed(connectionId, message, userData);
}
@Override
protected void connectionFailed(String connectionId, String message,
Object userData) {
super.connectionFailed(connectionId, message, userData);
......
......@@ -39,7 +39,8 @@ import javax.management.*;
/*
This test checks that annotations produce Descriptor entries as
specified in javax.management.DescriptorKey. It does two things:
specified in javax.management.DescriptorKey and javax.management.DescriptorField.
It does the following:
- An annotation consisting of an int and a String, each with an
appropriate @DescriptorKey annotation, is placed on every program
......@@ -61,6 +62,10 @@ import javax.management.*;
The test checks that in each case the corresponding Descriptor
appears in the appropriate place inside the MBean's MBeanInfo.
- A @DescriptorFields annotation defining two fields is placed in the
same places and again the test checks that the two fields appear
in the corresponding MBean*Info objects.
- An annotation consisting of enough other types to ensure coverage
is placed on a getter. The test checks that the generated
MBeanAttributeInfo contains the corresponding Descriptor. The tested
......@@ -78,12 +83,6 @@ import javax.management.*;
public class AnnotationTest {
private static String failed = null;
// @Retention(RetentionPolicy.RUNTIME) @Inherited
// @Target(ElementType.METHOD)
// public static @interface DescriptorKey {
// String value();
// }
@Retention(RetentionPolicy.RUNTIME)
public static @interface Pair {
@DescriptorKey("x")
......@@ -112,11 +111,12 @@ public class AnnotationTest {
boolean[] booleanArrayValue();
}
/* We use the annotation @Pair(x = 3, y = "foo") everywhere, and this is
the Descriptor that it should produce: */
/* We use the annotations @Pair(x = 3, y = "foo")
and @DescriptorFields({"foo=bar", "baz="}) everywhere, and this is
the Descriptor that they should produce: */
private static Descriptor expectedDescriptor =
new ImmutableDescriptor(new String[] {"x", "y"},
new Object[] {3, "foo"});
new ImmutableDescriptor(new String[] {"x", "y", "foo", "baz"},
new Object[] {3, "foo", "bar", ""});
private static Descriptor expectedFullDescriptor =
new ImmutableDescriptor(new String[] {
......@@ -136,8 +136,10 @@ public class AnnotationTest {
});
@Pair(x = 3, y = "foo")
@DescriptorFields({"foo=bar", "baz="})
public static interface ThingMBean {
@Pair(x = 3, y = "foo")
@DescriptorFields({"foo=bar", "baz="})
@Full(classValue=Full.class,
enumValue=RetentionPolicy.RUNTIME,
booleanValue=false,
......@@ -149,32 +151,47 @@ public class AnnotationTest {
int getReadOnly();
@Pair(x = 3, y = "foo")
@DescriptorFields({"foo=bar", "baz="})
void setWriteOnly(int x);
@Pair(x = 3, y = "foo")
@DescriptorFields({"foo=bar", "baz="})
int getReadWrite1();
void setReadWrite1(int x);
@Pair(x = 3, y = "foo")
@DescriptorFields({"foo=bar", "baz="})
int getReadWrite2();
@Pair(x = 3, y = "foo")
@DescriptorFields({"foo=bar", "baz="})
void setReadWrite2(int x);
int getReadWrite3();
@Pair(x = 3, y = "foo")
@DescriptorFields({"foo=bar", "baz="})
void setReadWrite3(int x);
@Pair(x = 3, y = "foo")
int operation(@Pair(x = 3, y = "foo") int p1,
@Pair(x = 3, y = "foo") int p2);
@DescriptorFields({"foo=bar", "baz="})
int operation(@Pair(x = 3, y = "foo")
@DescriptorFields({"foo=bar", "baz="})
int p1,
@Pair(x = 3, y = "foo")
@DescriptorFields({"foo=bar", "baz="})
int p2);
}
public static class Thing implements ThingMBean {
@Pair(x = 3, y = "foo")
@DescriptorFields({"foo=bar", "baz="})
public Thing() {}
@Pair(x = 3, y = "foo")
public Thing(@Pair(x = 3, y = "foo") int p1) {}
@DescriptorFields({"foo=bar", "baz="})
public Thing(
@Pair(x = 3, y = "foo")
@DescriptorFields({"foo=bar", "baz="})
int p1) {}
public int getReadOnly() {return 0;}
......@@ -193,14 +210,20 @@ public class AnnotationTest {
}
@Pair(x = 3, y = "foo")
@DescriptorFields({"foo=bar", "baz="})
public static interface ThingMXBean extends ThingMBean {}
public static class ThingImpl implements ThingMXBean {
@Pair(x = 3, y = "foo")
@DescriptorFields({"foo=bar", "baz="})
public ThingImpl() {}
@Pair(x = 3, y = "foo")
public ThingImpl(@Pair(x = 3, y = "foo") int p1) {}
@DescriptorFields({"foo=bar", "baz="})
public ThingImpl(
@Pair(x = 3, y = "foo")
@DescriptorFields({"foo=bar", "baz="})
int p1) {}
public int getReadOnly() {return 0;}
......@@ -218,6 +241,79 @@ public class AnnotationTest {
public int operation(int p1, int p2) {return 0;}
}
@Retention(RetentionPolicy.RUNTIME)
public static @interface DefaultTest {
@DescriptorKey(value = "string1", omitIfDefault = true)
String string1() default "";
@DescriptorKey(value = "string2", omitIfDefault = true)
String string2() default "tiddly pom";
@DescriptorKey(value = "int", omitIfDefault = true)
int intx() default 23;
@DescriptorKey(value = "intarray1", omitIfDefault = true)
int[] intArray1() default {};
@DescriptorKey(value = "intarray2", omitIfDefault = true)
int[] intArray2() default {1, 2};
@DescriptorKey(value = "stringarray1", omitIfDefault = true)
String[] stringArray1() default {};
@DescriptorKey(value = "stringarray2", omitIfDefault = true)
String[] stringArray2() default {"foo", "bar"};
}
@Retention(RetentionPolicy.RUNTIME)
public static @interface Expect {
String[] value() default {};
}
public static interface DefaultMBean {
@DefaultTest
@Expect()
public void a();
@DefaultTest(string1="")
@Expect()
public void b();
@DefaultTest(string1="nondefault")
@Expect("string1=nondefault")
public void c();
@DefaultTest(string2="tiddly pom")
@Expect()
public void d();
@DefaultTest(intx=23)
@Expect()
public void e();
@DefaultTest(intx=34)
@Expect("int=34")
public void f();
@DefaultTest(intArray1={})
@Expect()
public void g();
@DefaultTest(intArray1={2,3})
@Expect("intarray1=[2, 3]")
public void h();
@DefaultTest(intArray2={})
@Expect("intarray2=[]")
public void i();
@DefaultTest(stringArray1={})
@Expect()
public void j();
@DefaultTest(stringArray1={"foo"})
@Expect("stringarray1=[foo]")
public void k();
@DefaultTest(stringArray2={})
@Expect("stringarray2=[]")
public void l();
}
public static void main(String[] args) throws Exception {
System.out.println("Testing that annotations are correctly " +
"reflected in Descriptor entries");
......@@ -225,20 +321,62 @@ public class AnnotationTest {
MBeanServer mbs =
java.lang.management.ManagementFactory.getPlatformMBeanServer();
ObjectName on = new ObjectName("a:b=c");
Thing thing = new Thing();
mbs.registerMBean(thing, on);
check(mbs, on);
mbs.unregisterMBean(on);
ThingImpl thingImpl = new ThingImpl();
mbs.registerMBean(thingImpl, on);
Descriptor d = mbs.getMBeanInfo(on).getDescriptor();
if (!d.getFieldValue("mxbean").equals("true")) {
System.out.println("NOT OK: expected MXBean");
failed = "Expected MXBean";
}
check(mbs, on);
System.out.println();
System.out.println("Testing that omitIfDefault works");
DefaultMBean defaultImpl = (DefaultMBean) Proxy.newProxyInstance(
DefaultMBean.class.getClassLoader(),
new Class<?>[] {DefaultMBean.class},
new InvocationHandler(){
public Object invoke(Object proxy, Method method, Object[] args) {
return null;
}
});
DynamicMBean mbean = new StandardMBean(defaultImpl, DefaultMBean.class);
MBeanOperationInfo[] ops = mbean.getMBeanInfo().getOperations();
for (MBeanOperationInfo op : ops) {
String name = op.getName();
Expect expect =
DefaultMBean.class.getMethod(name).getAnnotation(Expect.class);
Descriptor opd = op.getDescriptor();
List<String> fields = new ArrayList<String>();
for (String fieldName : opd.getFieldNames()) {
Object value = opd.getFieldValue(fieldName);
String s = Arrays.deepToString(new Object[] {value});
s = s.substring(1, s.length() - 1);
fields.add(fieldName + "=" + s);
}
Descriptor opds = new ImmutableDescriptor(fields.toArray(new String[0]));
Descriptor expd = new ImmutableDescriptor(expect.value());
if (opds.equals(expd))
System.out.println("OK: op " + name + ": " + opds);
else {
String failure = "Bad descriptor for op " + name + ": " +
"expected " + expd + ", got " + opds;
System.out.println("NOT OK: " + failure);
failed = failure;
}
}
System.out.println();
if (failed == null)
System.out.println("Test passed");
else if (true)
throw new Exception("TEST FAILED: " + failed);
else
System.out.println("Test disabled until 6221321 implemented");
throw new Exception("TEST FAILED: " + failed);
}
private static void check(MBeanServer mbs, ObjectName on) throws Exception {
......@@ -295,151 +433,4 @@ public class AnnotationTest {
for (DescriptorRead x : xx)
check(x);
}
public static class AnnotatedMBean extends StandardMBean {
<T> AnnotatedMBean(T resource, Class<T> interfaceClass, boolean mx) {
super(resource, interfaceClass, mx);
}
private static final String[] attrPrefixes = {"get", "set", "is"};
protected void cacheMBeanInfo(MBeanInfo info) {
MBeanAttributeInfo[] attrs = info.getAttributes();
MBeanOperationInfo[] ops = info.getOperations();
for (int i = 0; i < attrs.length; i++) {
MBeanAttributeInfo attr = attrs[i];
String name = attr.getName();
Descriptor d = attr.getDescriptor();
Method m;
if ((m = getMethod("get" + name)) != null)
d = ImmutableDescriptor.union(d, descriptorFor(m));
if (attr.getType().equals("boolean") &&
(m = getMethod("is" + name)) != null)
d = ImmutableDescriptor.union(d, descriptorFor(m));
if ((m = getMethod("set" + name, attr)) != null)
d = ImmutableDescriptor.union(d, descriptorFor(m));
if (!d.equals(attr.getDescriptor())) {
attrs[i] =
new MBeanAttributeInfo(name, attr.getType(),
attr.getDescription(), attr.isReadable(),
attr.isWritable(), attr.isIs(), d);
}
}
for (int i = 0; i < ops.length; i++) {
MBeanOperationInfo op = ops[i];
String name = op.getName();
Descriptor d = op.getDescriptor();
MBeanParameterInfo[] params = op.getSignature();
Method m = getMethod(name, params);
if (m != null) {
d = ImmutableDescriptor.union(d, descriptorFor(m));
Annotation[][] annots = m.getParameterAnnotations();
for (int pi = 0; pi < params.length; pi++) {
MBeanParameterInfo param = params[pi];
Descriptor pd =
ImmutableDescriptor.union(param.getDescriptor(),
descriptorFor(annots[pi]));
params[pi] = new MBeanParameterInfo(param.getName(),
param.getType(), param.getDescription(), pd);
}
op = new MBeanOperationInfo(op.getName(),
op.getDescription(), params, op.getReturnType(),
op.getImpact(), d);
if (!ops[i].equals(op))
ops[i] = op;
}
}
Descriptor id = descriptorFor(getMBeanInterface());
info = new MBeanInfo(info.getClassName(), info.getDescription(),
attrs, info.getConstructors(), ops, info.getNotifications(),
ImmutableDescriptor.union(id, info.getDescriptor()));
super.cacheMBeanInfo(info);
}
private Descriptor descriptorFor(AnnotatedElement x) {
Annotation[] annots = x.getAnnotations();
return descriptorFor(annots);
}
private Descriptor descriptorFor(Annotation[] annots) {
if (annots.length == 0)
return ImmutableDescriptor.EMPTY_DESCRIPTOR;
Map<String, Object> descriptorMap = new HashMap<String, Object>();
for (Annotation a : annots) {
Class<? extends Annotation> c = a.annotationType();
Method[] elements = c.getMethods();
for (Method element : elements) {
DescriptorKey key =
element.getAnnotation(DescriptorKey.class);
if (key != null) {
String name = key.value();
Object value;
try {
value = element.invoke(a);
} catch (Exception e) {
// we don't expect this
throw new RuntimeException(e);
}
Object oldValue = descriptorMap.put(name, value);
if (oldValue != null && !oldValue.equals(value)) {
final String msg =
"Inconsistent values for descriptor field " +
name + " from annotations: " + value + " :: " +
oldValue;
throw new IllegalArgumentException(msg);
}
}
}
}
if (descriptorMap.isEmpty())
return ImmutableDescriptor.EMPTY_DESCRIPTOR;
else
return new ImmutableDescriptor(descriptorMap);
}
private Method getMethod(String name, MBeanFeatureInfo... params) {
Class<?> intf = getMBeanInterface();
ClassLoader loader = intf.getClassLoader();
Class[] classes = new Class[params.length];
for (int i = 0; i < params.length; i++) {
MBeanFeatureInfo param = params[i];
Descriptor d = param.getDescriptor();
String type = (String) d.getFieldValue("originalType");
if (type == null) {
if (param instanceof MBeanAttributeInfo)
type = ((MBeanAttributeInfo) param).getType();
else
type = ((MBeanParameterInfo) param).getType();
}
Class<?> c = primitives.get(type);
if (c == null) {
try {
c = Class.forName(type, false, loader);
} catch (ClassNotFoundException e) {
return null;
}
}
classes[i] = c;
}
try {
return intf.getMethod(name, classes);
} catch (Exception e) {
return null;
}
}
private static final Map<String, Class<?>> primitives =
new HashMap<String, Class<?>>();
static {
for (Class<?> c :
new Class[] {boolean.class, byte.class, short.class,
int.class, long.class, float.class,
double.class, char.class, void.class}) {
primitives.put(c.getName(), c);
}
}
}
}
/*
* 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.
*
* 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.
*/
/*
* @test
* @bug 5072267
* @summary Test that a context forwarder can be created and then installed.
* @author Eamonn McManus
*/
/* The specific thing we're testing for is that the forwarder can be created
* with a null "next", and then installed with a real "next". An earlier
* defect meant that in this case the simulated jmx.context// namespace had a
* null handler that never changed.
*/
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import javax.management.ClientContext;
import javax.management.MBeanServer;
import javax.management.MBeanServerConnection;
import javax.management.MBeanServerFactory;
import javax.management.ObjectName;
import javax.management.remote.JMXConnector;
import javax.management.remote.JMXConnectorFactory;
import javax.management.remote.JMXConnectorServer;
import javax.management.remote.JMXConnectorServerFactory;
import javax.management.remote.JMXServiceURL;
import javax.management.remote.MBeanServerForwarder;
public class ContextForwarderTest {
private static String failure;
public static void main(String[] args) throws Exception {
MBeanServer mbs = MBeanServerFactory.newMBeanServer();
JMXServiceURL url = new JMXServiceURL("service:jmx:rmi://");
Map<String, String> env = new HashMap<String, String>();
env.put(JMXConnectorServer.CONTEXT_FORWARDER, "false");
JMXConnectorServer cs = JMXConnectorServerFactory.newJMXConnectorServer(
url, env, mbs);
MBeanServerForwarder sysMBSF = cs.getSystemMBeanServerForwarder();
MBeanServerForwarder mbsf = ClientContext.newContextForwarder(mbs, sysMBSF);
sysMBSF.setMBeanServer(mbsf);
int localCount = mbs.getMBeanCount();
cs.start();
try {
JMXConnector cc = JMXConnectorFactory.connect(cs.getAddress());
MBeanServerConnection mbsc = cc.getMBeanServerConnection();
mbsc = ClientContext.withContext(mbsc, "foo", "bar");
int contextCount = mbsc.getMBeanCount();
if (localCount + 1 != contextCount) {
fail("Local MBean count %d, context MBean count %d",
localCount, contextCount);
}
Set<ObjectName> localNames =
new TreeSet<ObjectName>(mbs.queryNames(null, null));
ObjectName contextNamespaceObjectName =
new ObjectName(ClientContext.NAMESPACE + "//:type=JMXNamespace");
if (!localNames.add(contextNamespaceObjectName))
fail("Local names already contained context namespace handler");
Set<ObjectName> contextNames = mbsc.queryNames(null, null);
if (!localNames.equals(contextNames)) {
fail("Name set differs locally and in context: " +
"local: %s; context: %s", localNames, contextNames);
}
} finally {
cs.stop();
}
if (failure != null)
throw new Exception("TEST FAILED: " + failure);
else
System.out.println("TEST PASSED");
}
private static void fail(String msg, Object... params) {
failure = String.format(msg, params);
System.out.println("FAIL: " + failure);
}
}
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
# This is the default description ResourceBundle for MBeans in this package.
# Resources here override the descriptions specified with @Description
# but only when localization is happening and when there is not a more
# specific resource for the description (for example from MBeanDescriptions_fr).
WhatsitMBean.mbean = A whatsit
# This must be the same as WhatsitMBean.englishMBeanDescription for the
# purposes of this test.
此差异已折叠。
......@@ -200,8 +200,7 @@ public class CustomForwarderTest {
public static void main(String[] args) throws Exception {
MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
MBeanServerForwarder mbsf = EventClientDelegate.newForwarder();
mbsf.setMBeanServer(mbs);
MBeanServerForwarder mbsf = EventClientDelegate.newForwarder(mbs, null);
mbs = mbsf;
// for 1.5
......
......@@ -65,8 +65,7 @@ public class EventClientExecutorTest {
new NamedThreadFactory("LEASE"));
MBeanServer mbs = MBeanServerFactory.newMBeanServer();
MBeanServerForwarder mbsf = EventClientDelegate.newForwarder();
mbsf.setMBeanServer(mbs);
MBeanServerForwarder mbsf = EventClientDelegate.newForwarder(mbs, null);
mbs = mbsf;
EventClientDelegateMBean ecd = EventClientDelegate.getProxy(mbs);
......
......@@ -98,7 +98,7 @@ public class EventManagerTest {
succeed &= test(new EventClient(ecd,
new RMIPushEventRelay(ecd),
null, null,
EventClient.DEFAULT_LEASE_TIMEOUT));
EventClient.DEFAULT_REQUESTED_LEASE_TIME));
conn.close();
server.stop();
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册