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

Support for client contexts and also for localization of descriptions

Reviewed-by: dfuchs
This commit is contained in:
Eamonn McManus 2008-11-07 11:48:07 +01:00
parent cb4eb96188
commit ab227cb671
58 changed files with 4875 additions and 1547 deletions

View File

@ -69,9 +69,9 @@ public class ServiceName {
/** /**
* The version of the JMX specification implemented by this product. * The version of the JMX specification implemented by this product.
* <BR> * <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. * The vendor of the JMX specification implemented by this product.

View File

@ -41,7 +41,7 @@ public class EventParams {
@SuppressWarnings("cast") // cast for jdk 1.5 @SuppressWarnings("cast") // cast for jdk 1.5
public static long getLeaseTimeout() { public static long getLeaseTimeout() {
long timeout = EventClient.DEFAULT_LEASE_TIMEOUT; long timeout = EventClient.DEFAULT_REQUESTED_LEASE_TIME;
try { try {
final GetPropertyAction act = final GetPropertyAction act =
new GetPropertyAction(DEFAULT_LEASE_TIMEOUT); new GetPropertyAction(DEFAULT_LEASE_TIMEOUT);

View File

@ -29,6 +29,7 @@ import com.sun.jmx.remote.util.ClassLogger;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture; import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
/** /**
@ -143,9 +144,10 @@ public class LeaseManager {
private final Runnable callback; private final Runnable callback;
private ScheduledFuture<?> scheduled; // If null, the lease has expired. private ScheduledFuture<?> scheduled; // If null, the lease has expired.
private static final ThreadFactory threadFactory =
new DaemonThreadFactory("JMX LeaseManager %d");
private final ScheduledExecutorService executor private final ScheduledExecutorService executor
= Executors.newScheduledThreadPool(1, = Executors.newScheduledThreadPool(1, threadFactory);
new DaemonThreadFactory("JMX LeaseManager %d"));
private static final ClassLogger logger = private static final ClassLogger logger =
new ClassLogger("javax.management.event", "LeaseManager"); new ClassLogger("javax.management.event", "LeaseManager");

View File

@ -55,9 +55,19 @@ import javax.management.namespace.JMXNamespaces;
import javax.management.namespace.MBeanServerSupport; import javax.management.namespace.MBeanServerSupport;
import javax.management.remote.IdentityMBeanServerForwarder; 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 { public class SingleMBeanForwarder extends IdentityMBeanServerForwarder {
private final ObjectName mbeanName; private final ObjectName mbeanName;
private final boolean visible;
private DynamicMBean mbean; private DynamicMBean mbean;
private MBeanServer mbeanMBS = new MBeanServerSupport() { private MBeanServer mbeanMBS = new MBeanServerSupport() {
@ -85,10 +95,20 @@ public class SingleMBeanForwarder extends IdentityMBeanServerForwarder {
return null; 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.mbeanName = mbeanName;
this.visible = visible;
setSingleMBean(mbean); setSingleMBean(mbean);
} }
@ -213,8 +233,10 @@ public class SingleMBeanForwarder extends IdentityMBeanServerForwarder {
@Override @Override
public String[] getDomains() { public String[] getDomains() {
TreeSet<String> domainSet = String[] domains = super.getDomains();
new TreeSet<String>(Arrays.asList(super.getDomains())); if (!visible)
return domains;
TreeSet<String> domainSet = new TreeSet<String>(Arrays.asList(domains));
domainSet.add(mbeanName.getDomain()); domainSet.add(mbeanName.getDomain());
return domainSet.toArray(new String[domainSet.size()]); return domainSet.toArray(new String[domainSet.size()]);
} }
@ -222,7 +244,7 @@ public class SingleMBeanForwarder extends IdentityMBeanServerForwarder {
@Override @Override
public Integer getMBeanCount() { public Integer getMBeanCount() {
Integer count = super.getMBeanCount(); Integer count = super.getMBeanCount();
if (!super.isRegistered(mbeanName)) if (visible && !super.isRegistered(mbeanName))
count++; count++;
return count; return count;
} }
@ -284,7 +306,7 @@ public class SingleMBeanForwarder extends IdentityMBeanServerForwarder {
*/ */
private boolean applies(ObjectName pattern) { private boolean applies(ObjectName pattern) {
// we know pattern is not null. // we know pattern is not null.
if (!pattern.apply(mbeanName)) if (!visible || !pattern.apply(mbeanName))
return false; return false;
final String dompat = pattern.getDomain(); final String dompat = pattern.getDomain();
@ -306,10 +328,12 @@ public class SingleMBeanForwarder extends IdentityMBeanServerForwarder {
@Override @Override
public Set<ObjectInstance> queryMBeans(ObjectName name, QueryExp query) { public Set<ObjectInstance> queryMBeans(ObjectName name, QueryExp query) {
Set<ObjectInstance> names = super.queryMBeans(name, query); Set<ObjectInstance> names = super.queryMBeans(name, query);
if (name == null || applies(name) ) { if (visible) {
// Don't assume mbs.queryNames returns a writable set. if (name == null || applies(name) ) {
names = Util.cloneSet(names); // Don't assume mbs.queryNames returns a writable set.
names.addAll(mbeanMBS.queryMBeans(name, query)); names = Util.cloneSet(names);
names.addAll(mbeanMBS.queryMBeans(name, query));
}
} }
return names; return names;
} }
@ -317,10 +341,12 @@ public class SingleMBeanForwarder extends IdentityMBeanServerForwarder {
@Override @Override
public Set<ObjectName> queryNames(ObjectName name, QueryExp query) { public Set<ObjectName> queryNames(ObjectName name, QueryExp query) {
Set<ObjectName> names = super.queryNames(name, query); Set<ObjectName> names = super.queryNames(name, query);
if (name == null || applies(name)) { if (visible) {
// Don't assume mbs.queryNames returns a writable set. if (name == null || applies(name)) {
names = Util.cloneSet(names); // Don't assume mbs.queryNames returns a writable set.
names.addAll(mbeanMBS.queryNames(name, query)); names = Util.cloneSet(names);
names.addAll(mbeanMBS.queryNames(name, query));
}
} }
return names; return names;
} }

View File

@ -122,7 +122,7 @@ public final class JmxMBeanServer
* {@link javax.management.MBeanServerFactory#newMBeanServer(java.lang.String)} * {@link javax.management.MBeanServerFactory#newMBeanServer(java.lang.String)}
* instead. * instead.
* <p> * <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. * {@link #JmxMBeanServer(java.lang.String,javax.management.MBeanServer,javax.management.MBeanServerDelegate,boolean)} to enable them.
* </ul> * </ul>
* @param domain The default domain name used by this MBeanServer. * @param domain The default domain name used by this MBeanServer.
@ -239,7 +239,7 @@ public final class JmxMBeanServer
this.mBeanServerDelegateObject = delegate; this.mBeanServerDelegateObject = delegate;
this.outerShell = outer; this.outerShell = outer;
final Repository repository = new Repository(domain,fairLock); final Repository repository = new Repository(domain);
this.mbsInterceptor = this.mbsInterceptor =
new NamespaceDispatchInterceptor(outer, delegate, instantiator, new NamespaceDispatchInterceptor(outer, delegate, instantiator,
repository); repository);

View File

@ -1,354 +0,0 @@
/*
* 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;
}
}

View File

@ -49,11 +49,6 @@ public class ObjectNameRouter {
final int tlen; final int tlen;
final boolean identity; final boolean identity;
public ObjectNameRouter(String targetDirName) {
this(targetDirName,null);
}
/** Creates a new instance of ObjectNameRouter */ /** Creates a new instance of ObjectNameRouter */
public ObjectNameRouter(final String remove, final String add) { public ObjectNameRouter(final String remove, final String add) {
this.targetPrefix = (remove==null?"":remove); this.targetPrefix = (remove==null?"":remove);
@ -186,6 +181,4 @@ public class ObjectNameRouter {
b.append(NAMESPACE_SEPARATOR); b.append(NAMESPACE_SEPARATOR);
return b.toString(); return b.toString();
} }
} }

View File

@ -31,7 +31,6 @@ import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
import javax.management.MBeanServerConnection; import javax.management.MBeanServerConnection;
import javax.management.namespace.JMXNamespaces;
/** /**
@ -57,22 +56,14 @@ public class RoutingConnectionProxy
private static final Logger LOG = JmxProperties.NAMESPACE_LOGGER; 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 * Creates a new instance of RoutingConnectionProxy
*/ */
public RoutingConnectionProxy(MBeanServerConnection source, public RoutingConnectionProxy(MBeanServerConnection source,
String sourceDir, String sourceDir,
String targetDir, String targetDir,
boolean forwardsContext) { boolean probe) {
super(source,sourceDir,targetDir,forwardsContext); super(source, sourceDir, targetDir, probe);
if (LOG.isLoggable(Level.FINER)) if (LOG.isLoggable(Level.FINER))
LOG.finer("RoutingConnectionProxy for " + getSourceNamespace() + LOG.finer("RoutingConnectionProxy for " + getSourceNamespace() +
@ -85,15 +76,13 @@ public class RoutingConnectionProxy
final String sourceNs = getSourceNamespace(); final String sourceNs = getSourceNamespace();
String wrapped = String.valueOf(source()); String wrapped = String.valueOf(source());
if ("".equals(targetNs)) { if ("".equals(targetNs)) {
if (forwardsContext)
wrapped = "ClientContext.withDynamicContext("+wrapped+")";
return "JMXNamespaces.narrowToNamespace("+ return "JMXNamespaces.narrowToNamespace("+
wrapped+", \""+ wrapped+", \""+
sourceNs+"\")"; sourceNs+"\")";
} }
return this.getClass().getSimpleName()+"("+wrapped+", \""+ return this.getClass().getSimpleName()+"("+wrapped+", \""+
sourceNs+"\", \""+ sourceNs+"\", \""+
targetNs+"\", "+forwardsContext+")"; targetNs+"\")";
} }
static final RoutingProxyFactory static final RoutingProxyFactory
@ -102,22 +91,16 @@ public class RoutingConnectionProxy
<MBeanServerConnection,RoutingConnectionProxy>() { <MBeanServerConnection,RoutingConnectionProxy>() {
public RoutingConnectionProxy newInstance(MBeanServerConnection source, public RoutingConnectionProxy newInstance(MBeanServerConnection source,
String sourcePath, String targetPath, String sourcePath, String targetPath, boolean probe) {
boolean forwardsContext) {
return new RoutingConnectionProxy(source,sourcePath, return new RoutingConnectionProxy(source,sourcePath,
targetPath,forwardsContext); targetPath, probe);
}
public RoutingConnectionProxy newInstance(
MBeanServerConnection source, String sourcePath) {
return new RoutingConnectionProxy(source,sourcePath);
} }
}; };
public static MBeanServerConnection cd(MBeanServerConnection source, public static MBeanServerConnection cd(
String sourcePath) { MBeanServerConnection source, String sourcePath, boolean probe) {
return RoutingProxy.cd(RoutingConnectionProxy.class, FACTORY, return RoutingProxy.cd(RoutingConnectionProxy.class, FACTORY,
source, sourcePath); source, sourcePath, probe);
} }
} }

View File

@ -30,6 +30,7 @@ import java.io.IOException;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
import javax.management.InstanceNotFoundException;
import javax.management.MBeanException; import javax.management.MBeanException;
import javax.management.MBeanRegistrationException; import javax.management.MBeanRegistrationException;
@ -90,17 +91,9 @@ import javax.management.namespace.JMXNamespaces;
// targetNs=<encoded-context> // context must be removed from object name // targetNs=<encoded-context> // context must be removed from object name
// sourceNs="" // nothing to add... // 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, // Finally, in order to avoid too many layers of wrapping,
// RoutingConnectionProxy and RoutingServerProxy can be created through a // 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 // return a single RoutingProxy - rather than wrapping a RoutingProxy inside
// another RoutingProxy. See RoutingConnectionProxy.cd and // another RoutingProxy. See RoutingConnectionProxy.cd and
// RoutingServerProxy.cd // RoutingServerProxy.cd
@ -146,25 +139,27 @@ public abstract class RoutingProxy<T extends MBeanServerConnection>
private final T source; private final T source;
// The name space we're narrowing to (usually some name space in // 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; 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; private final String targetNs;
// The name of the JMXNamespace that handles the source name space // The name of the JMXNamespace that handles the source name space
private final ObjectName handlerName; private final ObjectName handlerName;
private final ObjectNameRouter router; private final ObjectNameRouter router;
final boolean forwardsContext;
private volatile String defaultDomain = null; private volatile String defaultDomain = null;
/** /**
* Creates a new instance of RoutingProxy * Creates a new instance of RoutingProxy
*/ */
protected RoutingProxy(T source, protected RoutingProxy(T source,
String sourceNs, String sourceNs,
String targetNs, String targetNs,
boolean forwardsContext) { boolean probe) {
if (source == null) throw new IllegalArgumentException("null"); if (source == null) throw new IllegalArgumentException("null");
this.sourceNs = JMXNamespaces.normalizeNamespaceName(sourceNs); this.sourceNs = JMXNamespaces.normalizeNamespaceName(sourceNs);
@ -177,13 +172,17 @@ public abstract class RoutingProxy<T extends MBeanServerConnection>
// System.err.println("sourceNs: "+sourceNs); // System.err.println("sourceNs: "+sourceNs);
this.handlerName = this.handlerName =
JMXNamespaces.getNamespaceObjectName(this.sourceNs); JMXNamespaces.getNamespaceObjectName(this.sourceNs);
try { if (probe) {
// System.err.println("handlerName: "+handlerName); try {
if (!source.isRegistered(handlerName)) if (!source.isRegistered(handlerName)) {
throw new IllegalArgumentException(sourceNs + InstanceNotFoundException infe =
": no such name space"); new InstanceNotFoundException(handlerName);
} catch (IOException x) { throw new IllegalArgumentException(sourceNs +
throw new IllegalArgumentException("source stale: "+x,x); ": no such name space", infe);
}
} catch (IOException x) {
throw new IllegalArgumentException("source stale: "+x,x);
}
} }
} }
this.source = source; this.source = source;
@ -191,7 +190,6 @@ public abstract class RoutingProxy<T extends MBeanServerConnection>
JMXNamespaces.normalizeNamespaceName(targetNs)); JMXNamespaces.normalizeNamespaceName(targetNs));
this.router = this.router =
new ObjectNameRouter(this.targetNs,this.sourceNs); new ObjectNameRouter(this.targetNs,this.sourceNs);
this.forwardsContext = forwardsContext;
if (LOG.isLoggable(Level.FINER)) if (LOG.isLoggable(Level.FINER))
LOG.finer("RoutingProxy for " + this.sourceNs + " created"); LOG.finer("RoutingProxy for " + this.sourceNs + " created");
@ -200,14 +198,6 @@ public abstract class RoutingProxy<T extends MBeanServerConnection>
@Override @Override
public T source() { return source; } public T source() { return source; }
ObjectNameRouter getObjectNameRouter() {
// TODO: uncomment this when contexts are added
// if (forwardsContext)
// return ObjectNameRouter.wrapWithContext(router);
// else
return router;
}
@Override @Override
public ObjectName toSource(ObjectName targetName) public ObjectName toSource(ObjectName targetName)
throws MalformedObjectNameException { throws MalformedObjectNameException {
@ -222,8 +212,7 @@ public abstract class RoutingProxy<T extends MBeanServerConnection>
if (defaultDomain != null) if (defaultDomain != null)
targetName = targetName.withDomain(defaultDomain); targetName = targetName.withDomain(defaultDomain);
} }
final ObjectNameRouter r = getObjectNameRouter(); return router.toSourceContext(targetName,true);
return r.toSourceContext(targetName,true);
} }
@Override @Override
@ -243,8 +232,7 @@ public abstract class RoutingProxy<T extends MBeanServerConnection>
public ObjectName toTarget(ObjectName sourceName) public ObjectName toTarget(ObjectName sourceName)
throws MalformedObjectNameException { throws MalformedObjectNameException {
if (sourceName == null) return null; if (sourceName == null) return null;
final ObjectNameRouter r = getObjectNameRouter(); return router.toTargetContext(sourceName,false);
return r.toTargetContext(sourceName,false);
} }
private Object getAttributeFromHandler(String attributeName) private Object getAttributeFromHandler(String attributeName)
@ -357,11 +345,8 @@ public abstract class RoutingProxy<T extends MBeanServerConnection>
// instance. // instance.
static interface RoutingProxyFactory<T extends MBeanServerConnection, static interface RoutingProxyFactory<T extends MBeanServerConnection,
R extends RoutingProxy<T>> { R extends RoutingProxy<T>> {
R newInstance(T source, public R newInstance(
String sourcePath, String targetPath, T source, String sourcePath, String targetPath, boolean probe);
boolean forwardsContext);
R newInstance(T source,
String sourcePath);
} }
// Performs a narrowDownToNamespace operation. // Performs a narrowDownToNamespace operation.
@ -377,7 +362,7 @@ public abstract class RoutingProxy<T extends MBeanServerConnection>
static <T extends MBeanServerConnection, R extends RoutingProxy<T>> static <T extends MBeanServerConnection, R extends RoutingProxy<T>>
R cd(Class<R> routingProxyClass, R cd(Class<R> routingProxyClass,
RoutingProxyFactory<T,R> factory, RoutingProxyFactory<T,R> factory,
T source, String sourcePath) { T source, String sourcePath, boolean probe) {
if (source == null) throw new IllegalArgumentException("null"); if (source == null) throw new IllegalArgumentException("null");
if (source.getClass().equals(routingProxyClass)) { if (source.getClass().equals(routingProxyClass)) {
// cast is OK here, but findbugs complains unless we use class.cast // 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 = final String path =
JMXNamespaces.concat(other.getSourceNamespace(), JMXNamespaces.concat(other.getSourceNamespace(),
sourcePath); sourcePath);
return factory.newInstance(other.source(),path,"", return factory.newInstance(other.source(), path, "", probe);
other.forwardsContext);
} }
// Note: we could do possibly something here - but it would involve // Note: we could do possibly something here - but it would involve
// removing part of targetDir, and possibly adding // removing part of targetDir, and possibly adding
// something to sourcePath. // something to sourcePath.
// Too complex to bother! => simply default to stacking... // Too complex to bother! => simply default to stacking...
} }
return factory.newInstance(source,sourcePath); return factory.newInstance(source, sourcePath, "", probe);
} }
} }

View File

@ -54,7 +54,6 @@ import javax.management.OperationsException;
import javax.management.QueryExp; import javax.management.QueryExp;
import javax.management.ReflectionException; import javax.management.ReflectionException;
import javax.management.loading.ClassLoaderRepository; import javax.management.loading.ClassLoaderRepository;
import javax.management.namespace.JMXNamespaces;
/** /**
* A RoutingServerProxy is an MBeanServer proxy that proxies a * A RoutingServerProxy is an MBeanServer proxy that proxies a
@ -76,19 +75,11 @@ public class RoutingServerProxy
extends RoutingProxy<MBeanServer> extends RoutingProxy<MBeanServer>
implements MBeanServer { implements MBeanServer {
/**
* Creates a new instance of RoutingServerProxy
*/
public RoutingServerProxy(MBeanServer source,
String sourceNs) {
this(source,sourceNs,"",false);
}
public RoutingServerProxy(MBeanServer source, public RoutingServerProxy(MBeanServer source,
String sourceNs, String sourceNs,
String targetNs, String targetNs,
boolean forwardsContext) { boolean probe) {
super(source,sourceNs,targetNs,forwardsContext); super(source, sourceNs, targetNs, probe);
} }
/** /**
@ -571,20 +562,15 @@ public class RoutingServerProxy
FACTORY = new RoutingProxyFactory<MBeanServer,RoutingServerProxy>() { FACTORY = new RoutingProxyFactory<MBeanServer,RoutingServerProxy>() {
public RoutingServerProxy newInstance(MBeanServer source, public RoutingServerProxy newInstance(MBeanServer source,
String sourcePath, String targetPath, String sourcePath, String targetPath, boolean probe) {
boolean forwardsContext) { return new RoutingServerProxy(
return new RoutingServerProxy(source,sourcePath, source, sourcePath, targetPath, probe);
targetPath,forwardsContext);
}
public RoutingServerProxy newInstance(
MBeanServer source, String sourcePath) {
return new RoutingServerProxy(source,sourcePath);
} }
}; };
public static MBeanServer cd(MBeanServer source, String sourcePath) { public static MBeanServer cd(
MBeanServer source, String sourcePath, boolean probe) {
return RoutingProxy.cd(RoutingServerProxy.class, FACTORY, return RoutingProxy.cd(RoutingServerProxy.class, FACTORY,
source, sourcePath); source, sourcePath, probe);
} }
} }

View File

@ -430,13 +430,11 @@ public class EventClientConnection implements InvocationHandler,
* The {@code EventClient} is created lazily, when it is needed * The {@code EventClient} is created lazily, when it is needed
* for the first time. If null, a default factory will be used * for the first time. If null, a default factory will be used
* (see {@link #createEventClient}). * (see {@link #createEventClient}).
* @return the * @return the MBeanServerConnection.
**/ **/
public static MBeanServerConnection getEventConnectionFor( public static MBeanServerConnection getEventConnectionFor(
MBeanServerConnection connection, MBeanServerConnection connection,
Callable<EventClient> eventClientFactory) { Callable<EventClient> eventClientFactory) {
// if c already uses an EventClient no need to create a new one.
//
if (connection instanceof EventClientFactory if (connection instanceof EventClientFactory
&& eventClientFactory != null) && eventClientFactory != null)
throw new IllegalArgumentException("connection already uses EventClient"); throw new IllegalArgumentException("connection already uses EventClient");

File diff suppressed because it is too large Load Diff

View File

@ -35,8 +35,8 @@ import java.io.Serializable;
// Javadoc imports: // Javadoc imports:
import java.lang.management.MemoryUsage; import java.lang.management.MemoryUsage;
import java.util.Arrays; import java.util.Arrays;
import java.util.Locale;
import java.util.ResourceBundle; import java.util.ResourceBundle;
import javax.management.openmbean.CompositeData; import javax.management.openmbean.CompositeData;
import javax.management.openmbean.MXBeanMappingFactory; import javax.management.openmbean.MXBeanMappingFactory;
import javax.management.openmbean.OpenMBeanAttributeInfoSupport; import javax.management.openmbean.OpenMBeanAttributeInfoSupport;
@ -118,19 +118,22 @@ import javax.management.openmbean.OpenType;
* deprecation, for example {@code "1.3 Replaced by the Capacity * deprecation, for example {@code "1.3 Replaced by the Capacity
* attribute"}.</td> * attribute"}.</td>
* *
* <tr id="descriptionResourceBundleBaseName"> * <tr><td id="descriptionResourceBundleBaseName"><i>descriptionResource<br>
* <td>descriptionResource<br>BundleBaseName</td><td>String</td><td>Any</td> * BundleBaseName</i></td><td>String</td><td>Any</td>
* *
* <td>The base name for the {@link ResourceBundle} in which the key given in * <td>The base name for the {@link ResourceBundle} in which the key given in
* the {@code descriptionResourceKey} field can be found, for example * 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"> * <tr><td id="descriptionResourceKey"><i>descriptionResourceKey</i></td>
* <td>descriptionResourceKey</td><td>String</td><td>Any</td> * <td>String</td><td>Any</td>
* *
* <td>A resource key for the description of this element. In * <td>A resource key for the description of this element. In
* conjunction with the {@code descriptionResourceBundleBaseName}, * 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> * <tr><td>enabled</td><td>String</td>
* <td>MBeanAttributeInfo<br>MBeanNotificationInfo<br>MBeanOperationInfo</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> * href="MBeanInfo.html#info-changed">{@code "jmx.mbean.info.changed"}</a>
* notification.</td> * 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 * <td>The time in milli-seconds that the MBeanInfo can reasonably be
* reasonably be expected to be unchanged. The value can be a {@code Long} * expected to be unchanged. The value can be a {@code Long} or a
* or a decimal string. This provides a hint from a DynamicMBean or any * decimal string. This provides a hint from a DynamicMBean or any
* MBean that does not define {@code immutableInfo} as {@code true} * MBean that does not define {@code immutableInfo} as {@code true}
* that the MBeanInfo is not likely to change within this period and * that the MBeanInfo is not likely to change within this period and
* therefore can be cached. When this field is missing or has the * 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 * <td>Legal values for an attribute or parameter. See
* {@link javax.management.openmbean}.</td> * {@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> * <tr id="maxValue"><td><i>maxValue</i><td>Object</td>
* <td>MBeanAttributeInfo<br>MBeanParameterInfo</td> * <td>MBeanAttributeInfo<br>MBeanParameterInfo</td>
* *

View File

@ -30,6 +30,7 @@ import com.sun.jmx.mbeanserver.MBeanInjector;
import com.sun.jmx.remote.util.ClassLogger; import com.sun.jmx.remote.util.ClassLogger;
import java.beans.BeanInfo; import java.beans.BeanInfo;
import java.beans.PropertyDescriptor; import java.beans.PropertyDescriptor;
import java.io.IOException;
import java.io.Serializable; import java.io.Serializable;
import java.lang.reflect.InvocationHandler; import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
@ -37,6 +38,7 @@ import java.lang.reflect.Method;
import java.lang.reflect.Proxy; import java.lang.reflect.Proxy;
import java.util.Map; import java.util.Map;
import java.util.TreeMap; import java.util.TreeMap;
import javax.management.namespace.JMXNamespaces;
import javax.management.openmbean.MXBeanMappingFactory; import javax.management.openmbean.MXBeanMappingFactory;
/** /**
@ -60,6 +62,21 @@ public class JMX {
*/ */
public static final String DEFAULT_VALUE_FIELD = "defaultValue"; 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 * The name of the <a href="Descriptor.html#immutableInfo">{@code
* immutableInfo}</a> field. * immutableInfo}</a> field.
@ -78,6 +95,12 @@ public class JMX {
*/ */
public static final String LEGAL_VALUES_FIELD = "legalValues"; 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 * The name of the <a href="Descriptor.html#maxValue">{@code
* maxValue}</a> field. * maxValue}</a> field.
@ -120,13 +143,12 @@ public class JMX {
* <p>Options to apply to an MBean proxy or to an instance of {@link * <p>Options to apply to an MBean proxy or to an instance of {@link
* StandardMBean}.</p> * StandardMBean}.</p>
* *
* <p>For example, to specify a custom {@link MXBeanMappingFactory} * <p>For example, to specify the "wrapped object visible" option for a
* for a {@code StandardMBean}, you might write this:</p> * {@code StandardMBean}, you might write this:</p>
* *
* <pre> * <pre>
* MXBeanMappingFactory factory = new MyMXBeanMappingFactory(); * StandardMBean.Options opts = new StandardMBean.Options();
* JMX.MBeanOptions opts = new JMX.MBeanOptions(); * opts.setWrappedObjectVisible(true);
* opts.setMXBeanMappingFactory(factory);
* StandardMBean mbean = new StandardMBean(impl, intf, opts); * StandardMBean mbean = new StandardMBean(impl, intf, opts);
* </pre> * </pre>
* *

View File

@ -25,6 +25,7 @@
package javax.management; package javax.management;
import com.sun.jmx.mbeanserver.Util;
import java.io.IOException; import java.io.IOException;
import java.io.StreamCorruptedException; import java.io.StreamCorruptedException;
import java.io.Serializable; import java.io.Serializable;
@ -37,6 +38,12 @@ import java.util.WeakHashMap;
import java.security.AccessController; import java.security.AccessController;
import java.security.PrivilegedAction; 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; 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 * <p>Since this class is immutable, the clone method is chiefly of
* interest to subclasses.</p> * interest to subclasses.</p>
*/ */
@Override
public Object clone () { public Object clone () {
try { try {
return super.clone() ; return super.clone() ;
@ -474,6 +482,7 @@ public class MBeanInfo implements Cloneable, Serializable, DescriptorRead {
return (Descriptor) nonNullDescriptor(descriptor).clone(); return (Descriptor) nonNullDescriptor(descriptor).clone();
} }
@Override
public String toString() { public String toString() {
return return
getClass().getName() + "[" + 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 * @return true if and only if <code>o</code> is an MBeanInfo that is equal
* to this one according to the rules above. * to this one according to the rules above.
*/ */
@Override
public boolean equals(Object o) { public boolean equals(Object o) {
if (o == this) if (o == this)
return true; return true;
@ -524,6 +534,7 @@ public class MBeanInfo implements Cloneable, Serializable, DescriptorRead {
Arrays.equals(p.fastGetNotifications(), fastGetNotifications())); Arrays.equals(p.fastGetNotifications(), fastGetNotifications()));
} }
@Override
public int hashCode() { public int hashCode() {
/* Since computing the hashCode is quite expensive, we cache it. /* Since computing the hashCode is quite expensive, we cache it.
If by some terrible misfortune the computed value is 0, the 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."); 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");
}
}
} }

View File

@ -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 * The MBean Server emits the following types of notifications: MBean registration, MBean
* de-registration. * unregistration.
* <P> * <P>
* To receive to MBeanServerNotifications, you need to be declared as listener to * To receive MBeanServerNotifications, you need to register a listener with
* the {@link javax.management.MBeanServerDelegate javax.management.MBeanServerDelegate} MBean * the {@link MBeanServerDelegate MBeanServerDelegate} MBean
* that represents the MBeanServer. The ObjectName of the MBeanServerDelegate is: * that represents the MBeanServer. The ObjectName of the MBeanServerDelegate is
* {@link MBeanServerDelegate#DELEGATE_NAME}, which is
* <CODE>JMImplementation:type=MBeanServerDelegate</CODE>. * <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 * @since 1.5
*/ */
public class MBeanServerNotification extends Notification { public class MBeanServerNotification extends Notification {

View File

@ -54,7 +54,7 @@ import com.sun.jmx.mbeanserver.GetPropertyAction;
* @since 1.5 * @since 1.5
*/ */
@SuppressWarnings("serial") // serialVersionUID is not constant @SuppressWarnings("serial") // serialVersionUID is not constant
public class Notification extends EventObject { public class Notification extends EventObject implements Cloneable {
// Serialization compatibility stuff: // Serialization compatibility stuff:
// Two serial forms are supported in this class. The selected form depends // Two serial forms are supported in this class. The selected form depends
@ -243,6 +243,26 @@ public class Notification extends EventObject {
this.message = message ; 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. * Sets the source.
* *
@ -285,8 +305,10 @@ public class Notification extends EventObject {
/** /**
* Get the notification type. * Get the notification type.
* *
* @return The notification type. It's a string expressed in a dot notation similar * @return The notification type. It's a string expressed in a dot notation
* to Java properties. An example of a notification type is network.alarm.router . * 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() { public String getType() {
return type ; return type ;
@ -317,14 +339,25 @@ public class Notification extends EventObject {
/** /**
* Get the notification message. * Get the notification message.
* *
* @return The message string of this notification object. It contains in a string, * @return The message string of this notification object.
* which could be the explanation of the notification for displaying to a user
* *
* @see #setMessage
*/ */
public String getMessage() { public String getMessage() {
return message ; 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. * Get the user data.
* *
@ -355,6 +388,7 @@ public class Notification extends EventObject {
* *
* @return A String representation of this notification. * @return A String representation of this notification.
*/ */
@Override
public String toString() { public String toString() {
return super.toString()+"[type="+type+"][message="+message+"]"; return super.toString()+"[type="+type+"][message="+message+"]";
} }

View File

@ -29,7 +29,6 @@ import com.sun.jmx.event.DaemonThreadFactory;
import com.sun.jmx.event.LeaseRenewer; import com.sun.jmx.event.LeaseRenewer;
import com.sun.jmx.event.ReceiverBuffer; import com.sun.jmx.event.ReceiverBuffer;
import com.sun.jmx.event.RepeatedSingletonJob; import com.sun.jmx.event.RepeatedSingletonJob;
import com.sun.jmx.namespace.JMXNamespaceUtils;
import com.sun.jmx.mbeanserver.PerThreadGroupPool; import com.sun.jmx.mbeanserver.PerThreadGroupPool;
import com.sun.jmx.remote.util.ClassLogger; import com.sun.jmx.remote.util.ClassLogger;
@ -58,7 +57,6 @@ import javax.management.NotificationBroadcasterSupport;
import javax.management.NotificationFilter; import javax.management.NotificationFilter;
import javax.management.NotificationListener; import javax.management.NotificationListener;
import javax.management.ObjectName; import javax.management.ObjectName;
import javax.management.remote.JMXConnector;
import javax.management.remote.NotificationResult; import javax.management.remote.NotificationResult;
import javax.management.remote.TargetedNotification; 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"; 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 * @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> * <p>Constructs a default {@code EventClient} object.</p>
@ -173,7 +172,7 @@ public class EventClient implements EventConsumer, NotificationManager {
*/ */
public EventClient(EventClientDelegateMBean delegate) public EventClient(EventClientDelegateMBean delegate)
throws IOException { 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. * If {@code null}, a default scheduler will be used.
* @param requestedLeaseTime The lease time used to keep this client alive * @param requestedLeaseTime The lease time used to keep this client alive
* in the {@link EventClientDelegateMBean}. A value of zero is equivalent * 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 IllegalArgumentException If {@code delegate} is null.
* @throws IOException If an I/O error occurs when communicating with the * @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) if (requestedLeaseTime == 0)
requestedLeaseTime = DEFAULT_LEASE_TIMEOUT; requestedLeaseTime = DEFAULT_REQUESTED_LEASE_TIME;
else if (requestedLeaseTime < 0) { else if (requestedLeaseTime < 0) {
throw new IllegalArgumentException( throw new IllegalArgumentException(
"Negative lease time: " + requestedLeaseTime); "Negative lease time: " + requestedLeaseTime);
@ -269,7 +268,13 @@ public class EventClient implements EventConsumer, NotificationManager {
new ScheduledThreadPoolExecutor(20, daemonThreadFactory); new ScheduledThreadPoolExecutor(20, daemonThreadFactory);
executor.setKeepAliveTime(1, TimeUnit.SECONDS); executor.setKeepAliveTime(1, TimeUnit.SECONDS);
executor.allowCoreThreadTimeOut(true); 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 // By default, a ScheduledThreadPoolExecutor will keep jobs
// in its queue even after they have been cancelled. They // in its queue even after they have been cancelled. They
// will only be removed when their scheduled time arrives. // 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 // this EventClient, this can lead to a moderately large number
// of objects remaining referenced until the renewal time // of objects remaining referenced until the renewal time
// arrives. Hence the above call, which removes the job from // 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 executor;
} }
}; };
return leaseRenewerThreadPool.getThreadPoolExecutor(create); 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 { final public EventClient call() throws Exception {
EventClientDelegateMBean ecd = EventClientDelegate.getProxy(conn); EventClientDelegateMBean ecd = EventClientDelegate.getProxy(conn);
return new EventClient(ecd, eventRelay, null, null, 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; 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> private static final PerThreadGroupPool<ScheduledThreadPoolExecutor>
leaseRenewerThreadPool = PerThreadGroupPool.make(); leaseRenewerThreadPool = PerThreadGroupPool.make();
} }

View File

@ -149,6 +149,7 @@ public class EventClientDelegate implements EventClientDelegateMBean {
// of a setMBeanServer on some other forwarder later in the chain. // of a setMBeanServer on some other forwarder later in the chain.
private static class Forwarder extends SingleMBeanForwarder { private static class Forwarder extends SingleMBeanForwarder {
private MBeanServer loopMBS;
private static class UnsupportedInvocationHandler private static class UnsupportedInvocationHandler
implements InvocationHandler { implements InvocationHandler {
@ -173,7 +174,11 @@ public class EventClientDelegate implements EventClientDelegateMBean {
private volatile boolean madeECD; private volatile boolean madeECD;
Forwarder() { Forwarder() {
super(OBJECT_NAME, makeUnsupportedECD()); super(OBJECT_NAME, makeUnsupportedECD(), true);
}
synchronized void setLoopMBS(MBeanServer loopMBS) {
this.loopMBS = loopMBS;
} }
@Override @Override
@ -186,7 +191,7 @@ public class EventClientDelegate implements EventClientDelegateMBean {
AccessController.doPrivileged( AccessController.doPrivileged(
new PrivilegedAction<EventClientDelegate>() { new PrivilegedAction<EventClientDelegate>() {
public EventClientDelegate run() { public EventClientDelegate run() {
return getEventClientDelegate(Forwarder.this); return getEventClientDelegate(loopMBS);
} }
}); });
DynamicMBean mbean = new StandardMBean( 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 * that are targeted for that MBean and handles them itself. All other
* requests are forwarded to the next element in the forwarder chain.</p> * 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 * @return a new {@code MBeanServerForwarder} that simulates the existence
* of an {@code EventClientDelegateMBean}. * of an {@code EventClientDelegateMBean}.
*
* @see javax.management.remote.JMXConnectorServer#installStandardForwarders
*/ */
public static MBeanServerForwarder newForwarder() { public static MBeanServerForwarder newForwarder(
return new Forwarder(); 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 classes
// ------------------------------------ // ------------------------------------
private class ClientInfo { private class ClientInfo {
String clientId; final String clientId;
EventBuffer buffer; final NotificationListener clientListener;
NotificationListener clientListener; final Map<Integer, AddedListener> listenerInfoMap =
Map<Integer, AddedListener> listenerInfoMap =
new HashMap<Integer, AddedListener>(); new HashMap<Integer, AddedListener>();
ClientInfo(String clientId, EventForwarder forwarder) { ClientInfo(String clientId, EventForwarder forwarder) {
@ -703,7 +742,8 @@ public class EventClientDelegate implements EventClientDelegateMBean {
clientInfo = clientInfoMap.get(clientId); clientInfo = clientInfoMap.get(clientId);
if (clientInfo == null) { if (clientInfo == null) {
throw new EventClientNotFoundException("The client is not found."); throw new EventClientNotFoundException(
"Client not found (id " + clientId + ")");
} }
return clientInfo; return clientInfo;

View File

@ -51,7 +51,8 @@ import javax.management.remote.NotificationResult;
* and the MBean Server, that will intercept accesses to the Event Client * 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 * 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 * 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 * <li><p>A variant on the above is to replace the MBean Server that is
* used locally with a forwarder as described above. Since * used locally with a forwarder as described above. Since
@ -61,9 +62,7 @@ import javax.management.remote.NotificationResult;
* *
* <pre> * <pre>
* MBeanServer mbs = ManagementFactory.getPlatformMBeanServer(); // or whatever * MBeanServer mbs = ManagementFactory.getPlatformMBeanServer(); // or whatever
* MBeanServerForwarder mbsf = EventClientDelegate.newForwarder(); * mbs = EventClientDelegate.newForwarder(mbs, null);
* mbsf.setMBeanServer(mbs);
* mbs = mbsf;
* // now use mbs just as you did before, but it will have an EventClientDelegate * // now use mbs just as you did before, but it will have an EventClientDelegate
* </pre> * </pre>
* *

View File

@ -27,7 +27,6 @@ package javax.management.event;
import java.io.IOException; import java.io.IOException;
import java.util.concurrent.Executors; // for javadoc import java.util.concurrent.Executors; // for javadoc
import java.util.concurrent.ScheduledFuture;
/** /**
* This interface is used to specify a way to receive * This interface is used to specify a way to receive

View File

@ -83,8 +83,8 @@
* javax.management.event.EventClientDelegateMBean EventClientDelegateMBean} * javax.management.event.EventClientDelegateMBean EventClientDelegateMBean}
* must be registered in the MBean Server, or the connector server must * must be registered in the MBean Server, or the connector server must
* be configured to simulate the existence of this MBean, for example * be configured to simulate the existence of this MBean, for example
* using {@link javax.management.event.EventClientDelegate#newForwarder() * using {@link javax.management.event.EventClientDelegate#newForwarder
* EventClientDelegate.newForwarder()}. The standard RMI connector is so * EventClientDelegate.newForwarder}. The standard RMI connector is so
* configured by default. The {@code EventClientDelegateMBean} documentation * configured by default. The {@code EventClientDelegateMBean} documentation
* has further details.</p> * has further details.</p>
* *

View File

@ -26,21 +26,19 @@
package javax.management.namespace; package javax.management.namespace;
import com.sun.jmx.defaults.JmxProperties; import com.sun.jmx.defaults.JmxProperties;
import com.sun.jmx.namespace.JMXNamespaceUtils;
import com.sun.jmx.namespace.ObjectNameRouter; import com.sun.jmx.namespace.ObjectNameRouter;
import com.sun.jmx.namespace.serial.RewritingProcessor; import com.sun.jmx.namespace.serial.RewritingProcessor;
import com.sun.jmx.namespace.RoutingConnectionProxy; import com.sun.jmx.namespace.RoutingConnectionProxy;
import com.sun.jmx.namespace.RoutingServerProxy; import com.sun.jmx.namespace.RoutingServerProxy;
import java.io.IOException;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
import javax.management.InstanceNotFoundException;
import javax.management.MBeanServer; import javax.management.MBeanServer;
import javax.management.MBeanServerConnection; import javax.management.MBeanServerConnection;
import javax.management.MalformedObjectNameException; import javax.management.MalformedObjectNameException;
import javax.management.ObjectName; import javax.management.ObjectName;
import javax.management.remote.JMXConnector;
/** /**
* Static constants and utility methods to help work with * Static constants and utility methods to help work with
@ -68,23 +66,6 @@ public class JMXNamespaces {
NAMESPACE_SEPARATOR.length(); 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 * Creates a new {@code MBeanServerConnection} proxy on a
* {@linkplain javax.management.namespace sub name space} * {@linkplain javax.management.namespace sub name space}
@ -96,15 +77,18 @@ public class JMXNamespaces {
* name space} in which to narrow. * name space} in which to narrow.
* @return A new {@code MBeanServerConnection} proxy that shows the content * @return A new {@code MBeanServerConnection} proxy that shows the content
* of that name space. * of that name space.
* @throws IllegalArgumentException if the name space does not exist, or * @throws IllegalArgumentException if either argument is null,
* if a proxy for that name space cannot be created. * 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( public static MBeanServerConnection narrowToNamespace(
MBeanServerConnection parent, MBeanServerConnection parent,
String namespace) { String namespace) {
if (LOG.isLoggable(Level.FINER)) if (LOG.isLoggable(Level.FINER))
LOG.finer("Making MBeanServerConnection for: " +namespace); 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. * of that name space.
* @throws IllegalArgumentException if either argument is null, * @throws IllegalArgumentException if either argument is null,
* or the name space does not exist, or if a proxy for that name space * 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, public static MBeanServer narrowToNamespace(MBeanServer parent,
String namespace) { String namespace) {
if (LOG.isLoggable(Level.FINER)) if (LOG.isLoggable(Level.FINER))
LOG.finer("Making NamespaceServerProxy for: " +namespace); LOG.finer("Making MBeanServer for: " +namespace);
return RoutingServerProxy.cd(parent,namespace); return RoutingServerProxy.cd(parent, namespace, true);
} }
/** /**
@ -266,7 +252,7 @@ public class JMXNamespaces {
ObjectNameRouter.normalizeNamespacePath(namespace,false, ObjectNameRouter.normalizeNamespacePath(namespace,false,
true,false); true,false);
try { 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 // IllegalArgumentException that contains just the supplied
// namespace instead of the whole ObjectName seems preferable. // namespace instead of the whole ObjectName seems preferable.
return ObjectName.getInstance(sourcePath+ return ObjectName.getInstance(sourcePath+

View File

@ -27,10 +27,10 @@ package javax.management.namespace;
import com.sun.jmx.defaults.JmxProperties; import com.sun.jmx.defaults.JmxProperties;
import com.sun.jmx.mbeanserver.Util; import com.sun.jmx.mbeanserver.Util;
import com.sun.jmx.namespace.JMXNamespaceUtils;
import com.sun.jmx.remote.util.EnvHelp; import com.sun.jmx.remote.util.EnvHelp;
import java.io.IOException; import java.io.IOException;
import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicLong;
@ -39,6 +39,7 @@ import java.util.logging.Logger;
import javax.management.AttributeChangeNotification; import javax.management.AttributeChangeNotification;
import javax.management.ClientContext;
import javax.management.InstanceNotFoundException; import javax.management.InstanceNotFoundException;
import javax.management.ListenerNotFoundException; import javax.management.ListenerNotFoundException;
import javax.management.MBeanNotificationInfo; import javax.management.MBeanNotificationInfo;
@ -220,17 +221,26 @@ public class JMXRemoteNamespace
initParentOnce(this); initParentOnce(this);
// URL must not be null. // URL must not be null.
this.jmxURL = JMXNamespaceUtils.checkNonNull(sourceURL,"url"); if (sourceURL == null)
throw new IllegalArgumentException("Null URL");
this.jmxURL = sourceURL;
this.broadcaster = this.broadcaster =
new NotificationBroadcasterSupport(connectNotification); new NotificationBroadcasterSupport(connectNotification);
// handles options // handles options
this.optionsMap = JMXNamespaceUtils.unmodifiableMap(optionsMap); this.optionsMap = unmodifiableMap(optionsMap);
// handles (dis)connection events // handles (dis)connection events
this.listener = new ConnectionListener(); 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 * Returns the {@code JMXServiceURL} that is (or will be) used to
* connect to the remote name space. <p> * 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 { throws IOException {
final JMXConnector c = newJMXConnector(jmxURL, env); final JMXConnector c = newJMXConnector(url, env);
c.connect(env); c.connect(env);
return c; return c;
} }
/** /**
* Creates a new JMXConnector with the specified {@code url} and * <p>Creates a new JMXConnector with the specified {@code url} and
* {@code env} options map. * {@code env} options map. The default implementation of this method
* <p> * returns {@link JMXConnectorFactory#newJMXConnector
* This method first calls {@link JMXConnectorFactory#newJMXConnector * JMXConnectorFactory.newJMXConnector(jmxURL, env)}. Subclasses can
* JMXConnectorFactory.newJMXConnector(jmxURL, env)} to obtain a new * override this method to customize behavior.</p>
* 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:
* <pre>
* class JMXRemoteSubNamespace extends JMXRemoteNamespace {
* private final String subnamespace;
* JMXRemoteSubNamespace(JMXServiceURL url,
* Map{@code <String,?>} env, String subnamespace) {
* super(url,options);
* 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)};
* }
* }
* </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>
* <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)};
* }
* }
* </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
* 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 url The JMXServiceURL of the remote server.
* @param optionsMap An unmodifiable options map that will be passed to the * @param optionsMap An options map that will be passed to the
* {@link JMXConnectorFactory} when {@linkplain * {@link JMXConnectorFactory} when {@linkplain
* JMXConnectorFactory#newJMXConnector creating} the * JMXConnectorFactory#newJMXConnector creating} the
* {@link JMXConnector} that can connect to the remote source * {@link JMXConnector} that can connect to the remote source
* MBean Server. * MBean Server.
* @return An unconnected JMXConnector to use to connect to the remote * @return A JMXConnector to use to connect to the remote server
* server * @throws IOException if the connector could not be created.
* @throws java.io.IOException if the connector could not be created.
* @see JMXConnectorFactory#newJMXConnector(javax.management.remote.JMXServiceURL, java.util.Map) * @see JMXConnectorFactory#newJMXConnector(javax.management.remote.JMXServiceURL, java.util.Map)
* @see #JMXRemoteNamespace * @see #JMXRemoteNamespace
*/ */
protected JMXConnector newJMXConnector(JMXServiceURL url, protected JMXConnector newJMXConnector(JMXServiceURL url,
Map<String,?> optionsMap) throws IOException { Map<String,?> optionsMap) throws IOException {
final JMXConnector c = return JMXConnectorFactory.newJMXConnector(jmxURL, optionsMap);
JMXConnectorFactory.newJMXConnector(jmxURL, optionsMap);
// TODO: uncomment this when contexts are added
// return ClientContext.withDynamicContext(c);
return c;
} }
/**
* <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, env);
* this.subnamespace = 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>
*
* <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 {
* 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}: 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 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 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 { public void connect() throws IOException {
LOG.fine("connecting..."); LOG.fine("connecting...");
final Map<String,Object> env = final Map<String,Object> env =
@ -590,7 +665,7 @@ public class JMXRemoteNamespace
try { try {
// XXX: We should probably document this... // XXX: We should probably document this...
// This allows to specify a loader name - which will be // This allows to specify a loader name - which will be
// retrieved from the paret MBeanServer. // retrieved from the parent MBeanServer.
defaultClassLoader = defaultClassLoader =
EnvHelp.resolveServerClassLoader(env,getMBeanServer()); EnvHelp.resolveServerClassLoader(env,getMBeanServer());
} catch (InstanceNotFoundException x) { } catch (InstanceNotFoundException x) {
@ -604,7 +679,7 @@ public class JMXRemoteNamespace
final JMXConnector aconn = connect(url,env); final JMXConnector aconn = connect(url,env);
final MBeanServerConnection msc; final MBeanServerConnection msc;
try { try {
msc = aconn.getMBeanServerConnection(); msc = getMBeanServerConnection(aconn);
aconn.addConnectionNotificationListener(listener,null,aconn); aconn.addConnectionNotificationListener(listener,null,aconn);
} catch (IOException io) { } catch (IOException io) {
close(aconn); close(aconn);

View File

@ -322,10 +322,12 @@ public class JMXConnectorFactory {
JMXConnectorProvider.class; JMXConnectorProvider.class;
final String protocol = serviceURL.getProtocol(); final String protocol = serviceURL.getProtocol();
final String providerClassName = "ClientProvider"; final String providerClassName = "ClientProvider";
final JMXServiceURL providerURL = serviceURL;
JMXConnectorProvider provider = JMXConnectorProvider provider = getProvider(providerURL, envcopy,
getProvider(serviceURL, envcopy, providerClassName, providerClassName,
targetInterface, loader); targetInterface,
loader);
IOException exception = null; IOException exception = null;
if (provider == null) { if (provider == null) {
@ -336,7 +338,7 @@ public class JMXConnectorFactory {
if (loader != null) { if (loader != null) {
try { try {
JMXConnector connection = JMXConnector connection =
getConnectorAsService(loader, serviceURL, envcopy); getConnectorAsService(loader, providerURL, envcopy);
if (connection != null) if (connection != null)
return connection; return connection;
} catch (JMXProviderException e) { } catch (JMXProviderException e) {
@ -345,8 +347,7 @@ public class JMXConnectorFactory {
exception = e; exception = e;
} }
} }
provider = provider = getProvider(protocol, PROTOCOL_PROVIDER_DEFAULT_PACKAGE,
getProvider(protocol, PROTOCOL_PROVIDER_DEFAULT_PACKAGE,
JMXConnectorFactory.class.getClassLoader(), JMXConnectorFactory.class.getClassLoader(),
providerClassName, targetInterface); providerClassName, targetInterface);
} }
@ -448,9 +449,10 @@ public class JMXConnectorFactory {
getProviderIterator(JMXConnectorProvider.class, loader); getProviderIterator(JMXConnectorProvider.class, loader);
JMXConnector connection; JMXConnector connection;
IOException exception = null; IOException exception = null;
while(providers.hasNext()) { while (providers.hasNext()) {
JMXConnectorProvider provider = providers.next();
try { try {
connection = providers.next().newJMXConnector(url, map); connection = provider.newJMXConnector(url, map);
return connection; return connection;
} catch (JMXProviderException e) { } catch (JMXProviderException e) {
throw e; throw e;
@ -553,4 +555,5 @@ public class JMXConnectorFactory {
private static String protocol2package(String protocol) { private static String protocol2package(String protocol) {
return protocol.replace('+', '.').replace('-', '_'); return protocol.replace('+', '.').replace('-', '_');
} }
} }

View File

@ -33,6 +33,7 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.NoSuchElementException; import java.util.NoSuchElementException;
import javax.management.ClientContext;
import javax.management.MBeanInfo; // for javadoc import javax.management.MBeanInfo; // for javadoc
import javax.management.MBeanNotificationInfo; import javax.management.MBeanNotificationInfo;
import javax.management.MBeanRegistration; import javax.management.MBeanRegistration;
@ -101,6 +102,56 @@ public abstract class JMXConnectorServer
public static final String DELEGATE_TO_EVENT_SERVICE = public static final String DELEGATE_TO_EVENT_SERVICE =
"jmx.remote.delegate.event.service"; "jmx.remote.delegate.event.service";
/**
* <p>Name of the attribute that specifies whether this connector
* server allows clients to communicate a context with each request.
* The value associated with this attribute, if any, must be a string
* that is equal to {@code "true"} or {@code "false"}, ignoring case.
* If it is {@code "true"}, then the connector server will simulate
* a namespace {@code jmx.context//}, as described in
* {@link ClientContext#newContextForwarder}. This namespace is needed
* for {@link ClientContext#withContext ClientContext.withContext} to
* function correctly.</p>
*
* <p>Not all connector servers will understand this attribute, but the
* standard {@linkplain javax.management.remote.rmi.RMIConnectorServer
* RMI Connector Server} does. For a connector server that understands
* this attribute, the default value is {@code "true"}.</p>
*
* @since 1.7
*/
public static final String CONTEXT_FORWARDER =
"jmx.remote.context.forwarder";
/**
* <p>Name of the attribute that specifies whether this connector server
* localizes the descriptions in the {@link MBeanInfo} object returned by
* {@link MBeanServer#getMBeanInfo MBeanServer.getMBeanInfo}, based on the
* locale communicated by the client.</p>
*
* <p>The value associated with this attribute, if any, must be a string
* that is equal to {@code "true"} or {@code "false"}, ignoring case.
* If it is {@code "true"}, then the connector server will localize
* {@code MBeanInfo} descriptions as specified in {@link
* ClientContext#newLocalizeMBeanInfoForwarder}.</p>
*
* <p>Not all connector servers will understand this attribute, but the
* standard {@linkplain javax.management.remote.rmi.RMIConnectorServer
* RMI Connector Server} does. For a connector server that understands
* this attribute, the default value is {@code "false"}.</p>
*
* <p>Because localization requires the client to be able to communicate
* its locale, it does not make sense to specify this attribute as
* {@code "true"} if {@link #CONTEXT_FORWARDER} is not also {@code "true"}.
* For a connector server that understands these attributes, specifying
* this inconsistent combination will result in an {@link
* IllegalArgumentException}.</p>
*
* @since 1.7
*/
public static final String LOCALIZE_MBEAN_INFO_FORWARDER =
"jmx.remote.localize.mbean.info";
/** /**
* <p>Name of the attribute that specifies whether this connector * <p>Name of the attribute that specifies whether this connector
* server simulates the existence of the {@link EventClientDelegate} * server simulates the existence of the {@link EventClientDelegate}
@ -155,7 +206,7 @@ public abstract class JMXConnectorServer
* to, or null if it is not yet attached to an MBean server. * to, or null if it is not yet attached to an MBean server.
* *
* @see #setMBeanServerForwarder * @see #setMBeanServerForwarder
* @see #getSystemMBeanServer * @see #getSystemMBeanServerForwarder
*/ */
public synchronized MBeanServer getMBeanServer() { public synchronized MBeanServer getMBeanServer() {
return userMBeanServer; return userMBeanServer;
@ -176,30 +227,36 @@ public abstract class JMXConnectorServer
* this method, the first occurrence in the chain of an object that is * this method, the first occurrence in the chain of an object that is
* {@linkplain Object#equals equal} to {@code mbsf} will have been * {@linkplain Object#equals equal} to {@code mbsf} will have been
* removed.</p> * removed.</p>
*
* @param mbsf the forwarder to remove * @param mbsf the forwarder to remove
*
* @throws NoSuchElementException if there is no occurrence of {@code mbsf} * @throws NoSuchElementException if there is no occurrence of {@code mbsf}
* in the chain. * in the chain.
* @throws IllegalArgumentException if {@code mbsf} is null. * @throws IllegalArgumentException if {@code mbsf} is null or is the
* {@linkplain #getSystemMBeanServerForwarder() system forwarder}.
*
* @since 1.7
*/ */
public synchronized void removeMBeanServerForwarder(MBeanServerForwarder mbsf) { public synchronized void removeMBeanServerForwarder(MBeanServerForwarder mbsf) {
if (mbsf == null) if (mbsf == null)
throw new IllegalArgumentException("Invalid null argument: mbsf"); throw new IllegalArgumentException("Invalid null argument: mbsf");
if (systemMBeanServerForwarder.equals(mbsf))
throw new IllegalArgumentException("Cannot remove system forwarder");
MBeanServerForwarder prev = null; MBeanServerForwarder prev = systemMBeanServerForwarder;
MBeanServer curr = systemMBeanServer; MBeanServer curr;
while (curr instanceof MBeanServerForwarder && !mbsf.equals(curr)) { while (true) {
prev = (MBeanServerForwarder) curr;
curr = prev.getMBeanServer(); curr = prev.getMBeanServer();
if (mbsf.equals(curr))
break;
if (curr instanceof MBeanServerForwarder)
prev = (MBeanServerForwarder) curr;
else
throw new NoSuchElementException("MBeanServerForwarder not in chain");
} }
if (!(curr instanceof MBeanServerForwarder)) MBeanServer next = mbsf.getMBeanServer();
throw new NoSuchElementException("MBeanServerForwarder not in chain"); prev.setMBeanServer(next);
MBeanServerForwarder deleted = (MBeanServerForwarder) curr; if (userMBeanServer == mbsf)
MBeanServer next = deleted.getMBeanServer();
if (prev != null)
prev.setMBeanServer(next);
if (systemMBeanServer == deleted)
systemMBeanServer = next;
if (userMBeanServer == deleted)
userMBeanServer = next; userMBeanServer = next;
} }
@ -209,66 +266,63 @@ public abstract class JMXConnectorServer
* the systemMBeanServer and userMBeanServer field declarations. * the systemMBeanServer and userMBeanServer field declarations.
*/ */
private void insertUserMBeanServer(MBeanServer mbs) { private void insertUserMBeanServer(MBeanServer mbs) {
MBeanServerForwarder lastSystemMBSF = null; MBeanServerForwarder lastSystemMBSF = systemMBeanServerForwarder;
for (MBeanServer mbsi = systemMBeanServer; while (true) {
mbsi != userMBeanServer; MBeanServer mbsi = lastSystemMBSF.getMBeanServer();
mbsi = lastSystemMBSF.getMBeanServer()) { if (mbsi == userMBeanServer)
break;
lastSystemMBSF = (MBeanServerForwarder) mbsi; lastSystemMBSF = (MBeanServerForwarder) mbsi;
} }
userMBeanServer = mbs; userMBeanServer = mbs;
if (lastSystemMBSF == null) lastSystemMBSF.setMBeanServer(mbs);
systemMBeanServer = mbs;
else
lastSystemMBSF.setMBeanServer(mbs);
} }
/** /**
* <p>Returns the first item in the chain of system and then user * <p>Returns the first item in the chain of system and then user
* forwarders. In the simplest case, a {@code JMXConnectorServer} * forwarders. There is a chain of {@link MBeanServerForwarder}s between
* is connected directly to an {@code MBeanServer}. But there can * a {@code JMXConnectorServer} and its {@code MBeanServer}. This chain
* also be a chain of {@link MBeanServerForwarder}s between the two. * consists of two sub-chains: first the <em>system chain</em> and then
* This chain consists of two sub-chains: first the <em>system chain</em> * the <em>user chain</em>. Incoming requests are given to the first
* and then the <em>user chain</em>. Incoming requests are given to the * forwarder in the system chain. Each forwarder can handle a request
* first forwarder in the system chain. Each forwarder can handle * itself, or more usually forward it to the next forwarder, perhaps with
* a request itself, or more usually forward it to the next forwarder, * some extra behavior such as logging or security checking before or after
* perhaps with some extra behavior such as logging or security * the forwarding. The last forwarder in the system chain is followed by
* checking before or after the forwarding. The last forwarder in * the first forwarder in the user chain.</p>
* the system chain is followed by the first forwarder in the user
* chain.</p>
* *
* <p>The <em>system chain</em> is usually * <p>The object returned by this method is the first forwarder in the
* defined by a connector server based on the environment Map; * system chain. For a given {@code JMXConnectorServer}, this method
* see {@link JMXConnectorServerFactory#newJMXConnectorServer}. Allowing the * always returns the same object, which simply forwards every request
* connector server to define its forwarders in this way ensures that * to the next object in the chain.</p>
* they are in the correct order - some forwarders need to be inserted *
* before others for correct behavior. It is possible to modify the * <p>Not all connector servers support a system chain of forwarders,
* system chain, for example using {@link #setSystemMBeanServerForwarder} or * although the standard {@linkplain
* {@link #removeMBeanServerForwarder}, but in that case the system * javax.management.remote.rmi.RMIConnectorServer RMI connector
* chain is no longer guaranteed to be correct.</p> * server} does. For those that do not, this method will throw {@code
* UnsupportedOperationException}. All
* connector servers do support a user chain of forwarders.</p>
*
* <p>The <em>system chain</em> is usually defined by a
* connector server based on the environment Map; see {@link
* JMXConnectorServerFactory#newJMXConnectorServer
* JMXConnectorServerFactory.newJMXConnectorServer}. Allowing
* the connector server to define its forwarders in this way
* ensures that they are in the correct order - some forwarders
* need to be inserted before others for correct behavior. It is
* possible to modify the system chain, for example using {@code
* connectorServer.getSystemMBeanServerForwarder().setMBeanServer(mbsf)} or
* {@link #removeMBeanServerForwarder removeMBeanServerForwarder}, but in
* that case the system chain is no longer guaranteed to be correct.</p>
* *
* <p>The <em>user chain</em> is defined by calling {@link * <p>The <em>user chain</em> is defined by calling {@link
* #setMBeanServerForwarder} to insert forwarders at the head of the user * #setMBeanServerForwarder setMBeanServerForwarder} to insert forwarders
* chain.</p> * at the head of the user chain.</p>
*
* <p>If there are no forwarders in either chain, then both
* {@link #getMBeanServer()} and {@code getSystemMBeanServer()} will
* return the {@code MBeanServer} for this connector server. If there
* are forwarders in the user chain but not the system chain, then
* both methods will return the first forwarder in the user chain.
* If there are forwarders in the system chain but not the user chain,
* then {@code getSystemMBeanServer()} will return the first forwarder
* in the system chain, and {@code getMBeanServer()} will return the
* {@code MBeanServer} for this connector server. Finally, if there
* are forwarders in each chain then {@code getSystemMBeanServer()}
* will return the first forwarder in the system chain, and {@code
* getMBeanServer()} will return the first forwarder in the user chain.</p>
* *
* <p>This code illustrates how the chains can be traversed:</p> * <p>This code illustrates how the chains can be traversed:</p>
* *
* <pre> * <pre>
* JMXConnectorServer cs; * JMXConnectorServer cs;
* System.out.println("system chain:"); * System.out.println("system chain:");
* MBeanServer mbs = cs.getSystemMBeanServer(); * MBeanServer mbs = cs.getSystemMBeanServerForwarder();
* while (true) { * while (true) {
* if (mbs == cs.getMBeanServer()) * if (mbs == cs.getMBeanServer())
* System.out.println("user chain:"); * System.out.println("user chain:");
@ -281,65 +335,40 @@ public abstract class JMXConnectorServer
* System.out.println("--MBean Server"); * System.out.println("--MBean Server");
* </pre> * </pre>
* *
* <h4>Note for connector server implementors</h4>
*
* <p>Existing connector server implementations can be updated to support
* a system chain of forwarders as follows:</p>
*
* <ul>
* <li><p>Override the {@link #supportsSystemMBeanServerForwarder()}
* method so that it returns true.</p>
*
* <li><p>Call {@link #installStandardForwarders} from the constructor of
* the connector server.</p>
*
* <li><p>Direct incoming requests to the result of {@link
* #getSystemMBeanServerForwarder()} instead of the result of {@link
* #getMBeanServer()}.</p>
* </ul>
*
* @return the first item in the system chain of forwarders. * @return the first item in the system chain of forwarders.
* *
* @see #setSystemMBeanServerForwarder * @throws UnsupportedOperationException if {@link
* #supportsSystemMBeanServerForwarder} returns false.
*
* @see #supportsSystemMBeanServerForwarder
* @see #setMBeanServerForwarder
*
* @since 1.7
*/ */
public synchronized MBeanServer getSystemMBeanServer() { public MBeanServerForwarder getSystemMBeanServerForwarder() {
return systemMBeanServer; if (!supportsSystemMBeanServerForwarder()) {
} throw new UnsupportedOperationException(
"System MBeanServerForwarder not supported by this " +
/** "connector server");
* <p>Inserts an object that intercepts requests for the MBean server }
* that arrive through this connector server. This object will be return systemMBeanServerForwarder;
* supplied as the <code>MBeanServer</code> for any new connection
* created by this connector server. Existing connections are
* unaffected.</p>
*
* <p>This method can be called more than once with different
* {@link MBeanServerForwarder} objects. The result is a chain
* of forwarders. The last forwarder added is the first in the chain.</p>
*
* <p>This method modifies the system chain of {@link MBeanServerForwarder}s.
* Usually user code should change the user chain instead, via
* {@link #setMBeanServerForwarder}.</p>
*
* <p>Not all connector servers support a system chain of forwarders.
* Calling this method on a connector server that does not will produce an
* {@link UnsupportedOperationException}.</p>
*
* <p>Suppose {@code mbs} is the result of {@link #getSystemMBeanServer()}
* before calling this method. If {@code mbs} is not null, then
* {@code mbsf.setMBeanServer(mbs)} will be called. If doing so
* produces an exception, this method throws the same exception without
* any other effect. If {@code mbs} is null, or if the call to
* {@code mbsf.setMBeanServer(mbs)} succeeds, then this method will
* return normally and {@code getSystemMBeanServer()} will then return
* {@code mbsf}.</p>
*
* <p>The result of {@link #getMBeanServer()} is unchanged by this method.</p>
*
* @param mbsf the new <code>MBeanServerForwarder</code>.
*
* @throws IllegalArgumentException if the call to {@link
* MBeanServerForwarder#setMBeanServer mbsf.setMBeanServer} fails
* with <code>IllegalArgumentException</code>, or if
* <code>mbsf</code> is null.
*
* @throws UnsupportedOperationException if
* {@link #supportsSystemMBeanServerForwarder} returns false.
*
* @see #getSystemMBeanServer()
*/
public synchronized void setSystemMBeanServerForwarder(
MBeanServerForwarder mbsf) {
if (mbsf == null)
throw new IllegalArgumentException("Invalid null argument: mbsf");
mustSupportSystemMBSF();
if (systemMBeanServer != null)
mbsf.setMBeanServer(systemMBeanServer);
systemMBeanServer = mbsf;
} }
/** /**
@ -350,19 +379,13 @@ public abstract class JMXConnectorServer
* *
* @return true if this connector server supports the system chain of * @return true if this connector server supports the system chain of
* forwarders. * forwarders.
*
* @since 1.7
*/ */
public boolean supportsSystemMBeanServerForwarder() { public boolean supportsSystemMBeanServerForwarder() {
return false; return false;
} }
private void mustSupportSystemMBSF() {
if (!supportsSystemMBeanServerForwarder()) {
throw new UnsupportedOperationException(
"System MBeanServerForwarder not supported by this " +
"connector server");
}
}
/** /**
* <p>Install {@link MBeanServerForwarder}s in the system chain * <p>Install {@link MBeanServerForwarder}s in the system chain
* based on the attributes in the given {@code Map}. A connector * based on the attributes in the given {@code Map}. A connector
@ -374,34 +397,90 @@ public abstract class JMXConnectorServer
* <ul> * <ul>
* *
* <li>If {@link #EVENT_CLIENT_DELEGATE_FORWARDER} is absent, or is * <li>If {@link #EVENT_CLIENT_DELEGATE_FORWARDER} is absent, or is
* present with the value {@code "true"}, then a forwarder with the * present with the value {@code "true"}, then a forwarder
* functionality of {@link EventClientDelegate#newForwarder} is inserted * equivalent to {@link EventClientDelegate#newForwarder
* at the start of the system chain.</li> * EventClientDelegate.newForwarder}{@code (sysMBSF.getMBeanServer(),
* sysMBSF)} is inserted at the start of the system chain,
* where {@code sysMBSF} is the object returned by {@link
* #getSystemMBeanServerForwarder()}. </li>
*
* <li>If {@link #LOCALIZE_MBEAN_INFO_FORWARDER} is present with the
* value {@code "true"}, then a forwarder equivalent to
* {@link ClientContext#newLocalizeMBeanInfoForwarder
* ClientContext.newLocalizeMBeanInfoForwarder}{@code
* (sysMBSF.getMBeanServer())} is inserted at the start of the system
* chain.</li>
*
* <li>If {@link #CONTEXT_FORWARDER} is absent, or is present with
* the value {@code "true"}, then a forwarder equivalent to
* {@link ClientContext#newContextForwarder
* ClientContext.newContextForwarder}{@code (sysMSBF.getMBeanServer(),
* sysMBSF)} is inserted at the tart of the system chain.</li>
* *
* </ul> * </ul>
* *
* <p>For {@code EVENT_CLIENT_DELEGATE_FORWARDER}, if the * <p>For {@code EVENT_CLIENT_DELEGATE_FORWARDER} and {@code
* attribute is absent from the {@code Map} and a system property * CONTEXT_FORWARDER}, if the attribute is absent from the {@code
* of the same name is defined, then the value of the system * Map} and a system property of the same name is defined, then
* property is used as if it were in the {@code Map}. * the value of the system property is used as if it were in the
* {@code Map}.
*
* <p>Since each forwarder is inserted at the start of the chain,
* the final order of the forwarders is the <b>reverse</b> of the order
* above. This is important, because the {@code
* LOCALIZE_MBEAN_INFO_FORWARDER} can only work if the {@code
* CONTEXT_FORWARDER} has already installed the remote client's locale
* in the {@linkplain ClientContext#getContext context} of the current
* thread.</p>
* *
* <p>Attributes in {@code env} that are not listed above are ignored * <p>Attributes in {@code env} that are not listed above are ignored
* by this method.</p> * by this method.</p>
* *
* @throws UnsupportedOperationException if {@link * @throws UnsupportedOperationException if {@link
* #supportsSystemMBeanServerForwarder} is false. * #supportsSystemMBeanServerForwarder} is false.
*
* @throws IllegalArgumentException if the relevant attributes in {@code env} are
* inconsistent, for example if {@link #LOCALIZE_MBEAN_INFO_FORWARDER} is
* {@code "true"} but {@link #CONTEXT_FORWARDER} is {@code "false"}; or
* if one of the attributes has an illegal value.
*
* @since 1.7
*/ */
protected void installStandardForwarders(Map<String, ?> env) { protected void installStandardForwarders(Map<String, ?> env) {
mustSupportSystemMBSF(); MBeanServerForwarder sysMBSF = getSystemMBeanServerForwarder();
// Remember that forwarders must be added in reverse order! // Remember that forwarders must be added in reverse order!
boolean ecd = EnvHelp.computeBooleanFromString( boolean ecd = EnvHelp.computeBooleanFromString(
env, EVENT_CLIENT_DELEGATE_FORWARDER, false, true); env, EVENT_CLIENT_DELEGATE_FORWARDER, false, true);
boolean localize = EnvHelp.computeBooleanFromString(
env, LOCALIZE_MBEAN_INFO_FORWARDER, false, false);
boolean context = EnvHelp.computeBooleanFromString(
env, CONTEXT_FORWARDER, false, true);
if (localize && !context) {
throw new IllegalArgumentException(
"Inconsistent environment parameters: " +
LOCALIZE_MBEAN_INFO_FORWARDER + "=\"true\" requires " +
CONTEXT_FORWARDER + "=\"true\"");
}
if (ecd) { if (ecd) {
MBeanServerForwarder mbsf = EventClientDelegate.newForwarder(); MBeanServerForwarder mbsf = EventClientDelegate.newForwarder(
setSystemMBeanServerForwarder(mbsf); sysMBSF.getMBeanServer(), sysMBSF);
sysMBSF.setMBeanServer(mbsf);
}
if (localize) {
MBeanServerForwarder mbsf = ClientContext.newLocalizeMBeanInfoForwarder(
sysMBSF.getMBeanServer());
sysMBSF.setMBeanServer(mbsf);
}
if (context) {
MBeanServerForwarder mbsf = ClientContext.newContextForwarder(
sysMBSF.getMBeanServer(), sysMBSF);
sysMBSF.setMBeanServer(mbsf);
} }
} }
@ -473,6 +552,7 @@ public abstract class JMXConnectorServer
* *
* @return the array of possible notifications. * @return the array of possible notifications.
*/ */
@Override
public MBeanNotificationInfo[] getNotificationInfo() { public MBeanNotificationInfo[] getNotificationInfo() {
final String[] types = { final String[] types = {
JMXConnectionNotification.OPENED, JMXConnectionNotification.OPENED,
@ -684,30 +764,29 @@ public abstract class JMXConnectorServer
* Fields describing the chains of forwarders (MBeanServerForwarders). * Fields describing the chains of forwarders (MBeanServerForwarders).
* In the general case, the forwarders look something like this: * In the general case, the forwarders look something like this:
* *
* systemMBeanServer userMBeanServer * userMBeanServer
* | | * |
* v v * v
* mbsf1 -> mbsf2 -> mbsf3 -> mbsf4 -> mbsf5 -> mbs * systemMBeanServerForwarder -> mbsf2 -> mbsf3 -> mbsf4 -> mbsf5 -> mbs
* *
* Here, each mbsfi is an MBeanServerForwarder, and the arrows * Here, each mbsfi is an MBeanServerForwarder, and the arrows
* illustrate its getMBeanServer() method. The last MBeanServerForwarder * illustrate its getMBeanServer() method. The last MBeanServerForwarder
* can point to an MBeanServer that is not instanceof MBeanServerForwarder, * can point to an MBeanServer that is not instanceof MBeanServerForwarder,
* here mbs. * here mbs.
* *
* Initially, the chain can be empty if this JMXConnectorServer was * The system chain is never empty because it always has at least
* constructed without an MBeanServer. In this case, both systemMBS * systemMBeanServerForwarder. Initially, the user chain can be empty if
* and userMBS will be null. If there is initially an MBeanServer, * this JMXConnectorServer was constructed without an MBeanServer. In
* then both systemMBS and userMBS will point to it. * this case, userMBS will be null. If there is initially an MBeanServer,
* userMBS will point to it.
* *
* Whenever userMBS is changed, the system chain must be updated. If there * Whenever userMBS is changed, the system chain must be updated. Before
* are forwarders in the system chain (between systemMBS and userMBS in the * the update, the last forwarder in the system chain points to the old
* picture above), then the last one must point to the old value of userMBS * value of userMBS (possibly null). It must be updated to point to
* (possibly null). It must be updated to point to the new value. If there * the new value. The invariant is that starting from systemMBSF and
* are no forwarders in the system chain, then systemMBS must be updated to * repeatedly calling MBSF.getMBeanServer() you will end up at userMBS.
* the new value of userMBS. The invariant is that starting from systemMBS * The implication is that you will not see any MBeanServer object on the
* and repeatedly calling MBSF.getMBeanServer() you will end up at * way that is not also an MBeanServerForwarder.
* userMBS. The implication is that you will not see any MBeanServer
* object on the way that is not also an MBeanServerForwarder.
* *
* The method insertUserMBeanServer contains the logic to change userMBS * The method insertUserMBeanServer contains the logic to change userMBS
* and adjust the system chain appropriately. * and adjust the system chain appropriately.
@ -716,7 +795,7 @@ public abstract class JMXConnectorServer
* MBeanServer, then userMBS becomes that MBeanServer, and the system * MBeanServer, then userMBS becomes that MBeanServer, and the system
* chain must be updated as just described. * chain must be updated as just described.
* *
* When systemMBS is updated, there is no effect on userMBS. The system * When systemMBSF is updated, there is no effect on userMBS. The system
* chain may contain forwarders even though the user chain is empty * chain may contain forwarders even though the user chain is empty
* (there is no MBeanServer). In that case an attempt to forward an * (there is no MBeanServer). In that case an attempt to forward an
* incoming request through the chain will fall off the end and fail with a * incoming request through the chain will fall off the end and fail with a
@ -726,7 +805,8 @@ public abstract class JMXConnectorServer
private MBeanServer userMBeanServer; private MBeanServer userMBeanServer;
private MBeanServer systemMBeanServer; private final MBeanServerForwarder systemMBeanServerForwarder =
new IdentityMBeanServerForwarder();
/** /**
* The name used to registered this server in an MBeanServer. * The name used to registered this server in an MBeanServer.

View File

@ -132,7 +132,7 @@ public interface JMXConnectorServerMBean {
* *
* <p>A connector server may support two chains of forwarders, * <p>A connector server may support two chains of forwarders,
* a system chain and a user chain. See {@link * 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>. * @param mbsf the new <code>MBeanServerForwarder</code>.
* *
@ -141,7 +141,7 @@ public interface JMXConnectorServerMBean {
* with <code>IllegalArgumentException</code>. This includes the * with <code>IllegalArgumentException</code>. This includes the
* case where <code>mbsf</code> is null. * case where <code>mbsf</code> is null.
* *
* @see JMXConnectorServer#setSystemMBeanServerForwarder * @see JMXConnectorServer#getSystemMBeanServerForwarder
*/ */
public void setMBeanServerForwarder(MBeanServerForwarder mbsf); public void setMBeanServerForwarder(MBeanServerForwarder mbsf);

View File

@ -383,7 +383,7 @@ public class RMIConnectorServer extends JMXConnectorServer {
try { try {
if (tracing) logger.trace("start", "setting default class loader"); if (tracing) logger.trace("start", "setting default class loader");
defaultClassLoader = EnvHelp.resolveServerClassLoader( defaultClassLoader = EnvHelp.resolveServerClassLoader(
attributes, getSystemMBeanServer()); attributes, getSystemMBeanServerForwarder());
} catch (InstanceNotFoundException infc) { } catch (InstanceNotFoundException infc) {
IllegalArgumentException x = new IllegalArgumentException x = new
IllegalArgumentException("ClassLoader not found: "+infc); IllegalArgumentException("ClassLoader not found: "+infc);
@ -398,7 +398,7 @@ public class RMIConnectorServer extends JMXConnectorServer {
else else
rmiServer = newServer(); rmiServer = newServer();
rmiServer.setMBeanServer(getSystemMBeanServer()); rmiServer.setMBeanServer(getSystemMBeanServerForwarder());
rmiServer.setDefaultClassLoader(defaultClassLoader); rmiServer.setDefaultClassLoader(defaultClassLoader);
rmiServer.setRMIConnectorServer(this); rmiServer.setRMIConnectorServer(this);
rmiServer.export(); rmiServer.export();
@ -592,31 +592,6 @@ public class RMIConnectorServer extends JMXConnectorServer {
return Collections.unmodifiableMap(map); 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} * {@inheritDoc}
* @return true, since this connector server does support a system chain * @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 here so that they are accessible to other classes in this package
even though they have protected access. */ even though they have protected access. */
@Override
protected void connectionOpened(String connectionId, String message, protected void connectionOpened(String connectionId, String message,
Object userData) { Object userData) {
super.connectionOpened(connectionId, message, userData); super.connectionOpened(connectionId, message, userData);
} }
@Override
protected void connectionClosed(String connectionId, String message, protected void connectionClosed(String connectionId, String message,
Object userData) { Object userData) {
super.connectionClosed(connectionId, message, userData); super.connectionClosed(connectionId, message, userData);
} }
@Override
protected void connectionFailed(String connectionId, String message, protected void connectionFailed(String connectionId, String message,
Object userData) { Object userData) {
super.connectionFailed(connectionId, message, userData); super.connectionFailed(connectionId, message, userData);

View File

@ -39,7 +39,8 @@ import javax.management.*;
/* /*
This test checks that annotations produce Descriptor entries as 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 - An annotation consisting of an int and a String, each with an
appropriate @DescriptorKey annotation, is placed on every program 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 The test checks that in each case the corresponding Descriptor
appears in the appropriate place inside the MBean's MBeanInfo. 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 - An annotation consisting of enough other types to ensure coverage
is placed on a getter. The test checks that the generated is placed on a getter. The test checks that the generated
MBeanAttributeInfo contains the corresponding Descriptor. The tested MBeanAttributeInfo contains the corresponding Descriptor. The tested
@ -78,12 +83,6 @@ import javax.management.*;
public class AnnotationTest { public class AnnotationTest {
private static String failed = null; private static String failed = null;
// @Retention(RetentionPolicy.RUNTIME) @Inherited
// @Target(ElementType.METHOD)
// public static @interface DescriptorKey {
// String value();
// }
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
public static @interface Pair { public static @interface Pair {
@DescriptorKey("x") @DescriptorKey("x")
@ -112,11 +111,12 @@ public class AnnotationTest {
boolean[] booleanArrayValue(); boolean[] booleanArrayValue();
} }
/* We use the annotation @Pair(x = 3, y = "foo") everywhere, and this is /* We use the annotations @Pair(x = 3, y = "foo")
the Descriptor that it should produce: */ and @DescriptorFields({"foo=bar", "baz="}) everywhere, and this is
the Descriptor that they should produce: */
private static Descriptor expectedDescriptor = private static Descriptor expectedDescriptor =
new ImmutableDescriptor(new String[] {"x", "y"}, new ImmutableDescriptor(new String[] {"x", "y", "foo", "baz"},
new Object[] {3, "foo"}); new Object[] {3, "foo", "bar", ""});
private static Descriptor expectedFullDescriptor = private static Descriptor expectedFullDescriptor =
new ImmutableDescriptor(new String[] { new ImmutableDescriptor(new String[] {
@ -136,8 +136,10 @@ public class AnnotationTest {
}); });
@Pair(x = 3, y = "foo") @Pair(x = 3, y = "foo")
@DescriptorFields({"foo=bar", "baz="})
public static interface ThingMBean { public static interface ThingMBean {
@Pair(x = 3, y = "foo") @Pair(x = 3, y = "foo")
@DescriptorFields({"foo=bar", "baz="})
@Full(classValue=Full.class, @Full(classValue=Full.class,
enumValue=RetentionPolicy.RUNTIME, enumValue=RetentionPolicy.RUNTIME,
booleanValue=false, booleanValue=false,
@ -149,32 +151,47 @@ public class AnnotationTest {
int getReadOnly(); int getReadOnly();
@Pair(x = 3, y = "foo") @Pair(x = 3, y = "foo")
@DescriptorFields({"foo=bar", "baz="})
void setWriteOnly(int x); void setWriteOnly(int x);
@Pair(x = 3, y = "foo") @Pair(x = 3, y = "foo")
@DescriptorFields({"foo=bar", "baz="})
int getReadWrite1(); int getReadWrite1();
void setReadWrite1(int x); void setReadWrite1(int x);
@Pair(x = 3, y = "foo") @Pair(x = 3, y = "foo")
@DescriptorFields({"foo=bar", "baz="})
int getReadWrite2(); int getReadWrite2();
@Pair(x = 3, y = "foo") @Pair(x = 3, y = "foo")
@DescriptorFields({"foo=bar", "baz="})
void setReadWrite2(int x); void setReadWrite2(int x);
int getReadWrite3(); int getReadWrite3();
@Pair(x = 3, y = "foo") @Pair(x = 3, y = "foo")
@DescriptorFields({"foo=bar", "baz="})
void setReadWrite3(int x); void setReadWrite3(int x);
@Pair(x = 3, y = "foo") @Pair(x = 3, y = "foo")
int operation(@Pair(x = 3, y = "foo") int p1, @DescriptorFields({"foo=bar", "baz="})
@Pair(x = 3, y = "foo") int p2); 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 { public static class Thing implements ThingMBean {
@Pair(x = 3, y = "foo") @Pair(x = 3, y = "foo")
@DescriptorFields({"foo=bar", "baz="})
public Thing() {} public Thing() {}
@Pair(x = 3, y = "foo") @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;} public int getReadOnly() {return 0;}
@ -193,14 +210,20 @@ public class AnnotationTest {
} }
@Pair(x = 3, y = "foo") @Pair(x = 3, y = "foo")
@DescriptorFields({"foo=bar", "baz="})
public static interface ThingMXBean extends ThingMBean {} public static interface ThingMXBean extends ThingMBean {}
public static class ThingImpl implements ThingMXBean { public static class ThingImpl implements ThingMXBean {
@Pair(x = 3, y = "foo") @Pair(x = 3, y = "foo")
@DescriptorFields({"foo=bar", "baz="})
public ThingImpl() {} public ThingImpl() {}
@Pair(x = 3, y = "foo") @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;} public int getReadOnly() {return 0;}
@ -218,6 +241,79 @@ public class AnnotationTest {
public int operation(int p1, int p2) {return 0;} 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 { public static void main(String[] args) throws Exception {
System.out.println("Testing that annotations are correctly " + System.out.println("Testing that annotations are correctly " +
"reflected in Descriptor entries"); "reflected in Descriptor entries");
@ -225,20 +321,62 @@ public class AnnotationTest {
MBeanServer mbs = MBeanServer mbs =
java.lang.management.ManagementFactory.getPlatformMBeanServer(); java.lang.management.ManagementFactory.getPlatformMBeanServer();
ObjectName on = new ObjectName("a:b=c"); ObjectName on = new ObjectName("a:b=c");
Thing thing = new Thing(); Thing thing = new Thing();
mbs.registerMBean(thing, on); mbs.registerMBean(thing, on);
check(mbs, on); check(mbs, on);
mbs.unregisterMBean(on); mbs.unregisterMBean(on);
ThingImpl thingImpl = new ThingImpl(); ThingImpl thingImpl = new ThingImpl();
mbs.registerMBean(thingImpl, on); 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); 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) if (failed == null)
System.out.println("Test passed"); System.out.println("Test passed");
else if (true)
throw new Exception("TEST FAILED: " + failed);
else 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 { private static void check(MBeanServer mbs, ObjectName on) throws Exception {
@ -295,151 +433,4 @@ public class AnnotationTest {
for (DescriptorRead x : xx) for (DescriptorRead x : xx)
check(x); 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);
}
}
}
} }

View File

@ -0,0 +1,103 @@
/*
* 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);
}
}

View File

@ -0,0 +1,534 @@
/*
* Copyright 2007 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 ContextTest
* @bug 5072267
* @summary Test client contexts.
* @author Eamonn McManus
* TODO: Try registering with a null name replaced by preRegister (for example
* from the MLet class) and see if it now works.
*/
import java.lang.management.ManagementFactory;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.Set;
import java.util.TreeMap;
import java.util.concurrent.Callable;
import javax.management.Attribute;
import javax.management.AttributeList;
import javax.management.ClientContext;
import javax.management.DynamicMBean;
import javax.management.JMX;
import javax.management.ListenerNotFoundException;
import javax.management.MBeanNotificationInfo;
import javax.management.MBeanRegistration;
import javax.management.MBeanServer;
import javax.management.MBeanServerDelegate;
import javax.management.Notification;
import javax.management.NotificationBroadcasterSupport;
import javax.management.NotificationFilter;
import javax.management.NotificationListener;
import javax.management.ObjectInstance;
import javax.management.ObjectName;
import javax.management.StandardMBean;
import javax.management.loading.MLet;
import javax.management.namespace.JMXNamespace;
import javax.management.remote.MBeanServerForwarder;
import static java.util.Collections.emptyMap;
import static java.util.Collections.singletonMap;
public class ContextTest {
private static String failure;
private static final Map<String, String> emptyContext = emptyMap();
public static interface ShowContextMBean {
public Map<String, String> getContext();
public Map<String, String> getCreationContext();
public Set<String> getCalledOps();
public String getThing();
public void setThing(String x);
public int add(int x, int y);
}
public static class ShowContext
extends NotificationBroadcasterSupport
implements ShowContextMBean, MBeanRegistration {
private final Map<String, String> creationContext;
private final Set<String> calledOps = new HashSet<String>();
public ShowContext() {
creationContext = getContext();
}
public Map<String, String> getContext() {
return ClientContext.getContext();
}
public Map<String, String> getCreationContext() {
return creationContext;
}
public Set<String> getCalledOps() {
return calledOps;
}
public String getThing() {
return "x";
}
public void setThing(String x) {
}
public int add(int x, int y) {
return x + y;
}
public ObjectName preRegister(MBeanServer server, ObjectName name) {
assertEquals("preRegister context", creationContext, getContext());
calledOps.add("preRegister");
return name;
}
public void postRegister(Boolean registrationDone) {
assertEquals("postRegister context", creationContext, getContext());
calledOps.add("postRegister");
}
// The condition checked here is not guaranteed universally true,
// but is true every time we unregister an instance of this MBean
// in this test.
public void preDeregister() throws Exception {
assertEquals("preDeregister context", creationContext, getContext());
}
public void postDeregister() {
assertEquals("postDeregister context", creationContext, getContext());
}
// Same remark as for preDeregister
@Override
public MBeanNotificationInfo[] getNotificationInfo() {
calledOps.add("getNotificationInfo");
return super.getNotificationInfo();
}
@Override
public void addNotificationListener(
NotificationListener listener, NotificationFilter filter, Object handback) {
calledOps.add("addNotificationListener");
super.addNotificationListener(listener, filter, handback);
}
@Override
public void removeNotificationListener(
NotificationListener listener)
throws ListenerNotFoundException {
calledOps.add("removeNL1");
super.removeNotificationListener(listener);
}
@Override
public void removeNotificationListener(
NotificationListener listener, NotificationFilter filter, Object handback)
throws ListenerNotFoundException {
calledOps.add("removeNL3");
super.removeNotificationListener(listener, filter, handback);
}
}
private static class LogRecord {
final String op;
final Object[] params;
final Map<String, String> context;
LogRecord(String op, Object[] params, Map<String, String> context) {
this.op = op;
this.params = params;
this.context = context;
}
@Override
public String toString() {
return op + Arrays.deepToString(params) + " " + context;
}
}
/*
* InvocationHandler that forwards all methods to a contained object
* but also records each forwarded method. This allows us to check
* that the appropriate methods were called with the appropriate
* parameters. It's similar to what's typically available in
* Mock Object frameworks.
*/
private static class LogIH implements InvocationHandler {
private final Object wrapped;
Queue<LogRecord> log = new LinkedList<LogRecord>();
LogIH(Object wrapped) {
this.wrapped = wrapped;
}
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
if (method.getDeclaringClass() != Object.class) {
LogRecord lr =
new LogRecord(
method.getName(), args, ClientContext.getContext());
log.add(lr);
}
try {
return method.invoke(wrapped, args);
} catch (InvocationTargetException e) {
throw e.getCause();
}
}
}
private static <T> T newSnoop(Class<T> wrappedClass, LogIH logIH) {
return wrappedClass.cast(Proxy.newProxyInstance(
wrappedClass.getClassLoader(),
new Class<?>[] {wrappedClass},
logIH));
}
public static void main(String[] args) throws Exception {
MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
System.out.println(mbs.queryNames(null, null));
ObjectName name = new ObjectName("a:b=c");
mbs.registerMBean(new ShowContext(), name);
final ShowContextMBean show =
JMX.newMBeanProxy(mbs, name, ShowContextMBean.class);
// Test local setting and getting within the MBeanServer
assertEquals("initial context", emptyContext, show.getContext());
ClientContext.doWithContext(singletonMap("foo", "bar"), new Callable<Void>() {
public Void call() {
assertEquals("context in doWithContext",
singletonMap("foo", "bar"), show.getContext());
return null;
}
});
assertEquals("initial context after doWithContext",
emptyContext, show.getContext());
String got = ClientContext.doWithContext(
singletonMap("foo", "baz"), new Callable<String>() {
public String call() {
return ClientContext.getContext().get("foo");
}
});
assertEquals("value extracted from context", "baz", got);
Map<String, String> combined = ClientContext.doWithContext(
singletonMap("foo", "baz"), new Callable<Map<String, String>>() {
public Map<String, String> call() throws Exception {
return ClientContext.doWithContext(
singletonMap("fred", "jim"),
new Callable<Map<String, String>>() {
public Map<String, String> call() {
return ClientContext.getContext();
}
});
}
});
assertEquals("nested doWithContext context",
singletonMap("fred", "jim"), combined);
final String ugh = "a!\u00c9//*=:\"% ";
ClientContext.doWithContext(singletonMap(ugh, ugh), new Callable<Void>() {
public Void call() {
assertEquals("context with tricky encoding",
singletonMap(ugh, ugh), show.getContext());
return null;
}
});
Map<String, String> ughMap = new TreeMap<String, String>();
ughMap.put(ugh, ugh);
ughMap.put("fred", "jim");
// Since this is a TreeMap and "fred" is greater than ugh (which begins
// with "a"), we will see the encoding of ugh first in the output string.
String encoded = ClientContext.encode(ughMap);
String expectedUghCoding = "a%21%C3%89%2F%2F%2A%3D%3A%22%25+";
String expectedUghMapCoding =
ClientContext.NAMESPACE + "//" + expectedUghCoding + "=" +
expectedUghCoding + ";fred=jim";
assertEquals("hairy context encoded as string",
expectedUghMapCoding, encoded);
// Wrap the MBeanServer with a context MBSF so we can test withContext.
// Also check the simulated namespace directly.
LogIH mbsIH = new LogIH(mbs);
MBeanServer snoopMBS = newSnoop(MBeanServer.class, mbsIH);
MBeanServerForwarder ctxMBS =
ClientContext.newContextForwarder(snoopMBS, null);
// The MBSF returned by ClientContext is actually a compound of two
// forwarders, but that is supposed to be hidden by changing the
// behaviour of get/setMBeanServer. Check that it is indeed so.
assertEquals("next MBS of context forwarder",
snoopMBS, ctxMBS.getMBeanServer());
// If the above assertion fails you may get a confusing message
// because the toString() of the two objects is likely to be the same
// so it will look as if they should be equal.
ctxMBS.setMBeanServer(null);
assertEquals("next MBS of context forwarder after setting it null",
null, ctxMBS.getMBeanServer());
ctxMBS.setMBeanServer(snoopMBS);
// The MBSF should look the same as the original MBeanServer except
// that it has the JMXNamespace for the simulated namespace.
Set<ObjectName> origNames = mbs.queryNames(null, null);
Set<ObjectName> mbsfNames = ctxMBS.queryNames(null, null);
assertEquals("number of MBeans returned by queryNames within forwarder",
origNames.size() + 1, mbsfNames.size());
assertEquals("MBeanCount within forwarder",
mbsfNames.size(), ctxMBS.getMBeanCount());
assertCalled(mbsIH, "queryNames", emptyContext);
assertCalled(mbsIH, "getMBeanCount", emptyContext);
ObjectName ctxNamespaceName = new ObjectName(
ClientContext.NAMESPACE + "//:" + JMXNamespace.TYPE_ASSIGNMENT);
origNames.add(ctxNamespaceName);
assertEquals("MBeans within forwarder", origNames, mbsfNames);
Set<String> domains = new HashSet<String>(Arrays.asList(ctxMBS.getDomains()));
assertEquals("domains include context namespace MBean",
true, domains.contains(ClientContext.NAMESPACE + "//"));
assertCalled(mbsIH, "getDomains", emptyContext);
// Now test ClientContext.withContext.
MBeanServer ughMBS = ClientContext.withContext(ctxMBS, ugh, ugh);
ShowContextMBean ughshow =
JMX.newMBeanProxy(ughMBS, name, ShowContextMBean.class);
Map<String, String> ughCtx = ughshow.getContext();
Map<String, String> ughExpect = singletonMap(ugh, ugh);
assertEquals("context seen by MBean accessed within namespace",
ughExpect, ughCtx);
assertCalled(mbsIH, "getAttribute", ughExpect, name, "Context");
MBeanServer cmbs = ClientContext.withContext(
ctxMBS, "mickey", "mouse");
ShowContextMBean cshow =
JMX.newMBeanProxy(cmbs, name, ShowContextMBean.class);
assertEquals("context seen by MBean accessed within namespace",
singletonMap("mickey", "mouse"), cshow.getContext());
MBeanServer ccmbs = ClientContext.withContext(
cmbs, "donald", "duck");
ShowContextMBean ccshow =
JMX.newMBeanProxy(ccmbs, name, ShowContextMBean.class);
Map<String, String> disney = new HashMap<String, String>();
disney.put("mickey", "mouse");
disney.put("donald", "duck");
assertEquals("context seen by MBean in nested namespace",
disney, ccshow.getContext());
// Test that all MBS ops produce reasonable results
ObjectName logger = new ObjectName("a:type=Logger");
DynamicMBean showMBean =
new StandardMBean(new ShowContext(), ShowContextMBean.class);
LogIH mbeanLogIH = new LogIH(showMBean);
DynamicMBean logMBean = newSnoop(DynamicMBean.class, mbeanLogIH);
ObjectInstance loggerOI = ccmbs.registerMBean(logMBean, logger);
assertEquals("ObjectName returned by createMBean",
logger, loggerOI.getObjectName());
// We get an getMBeanInfo call to determine the className in the
// ObjectInstance to return from registerMBean.
assertCalled(mbeanLogIH, "getMBeanInfo", disney);
ccmbs.getAttribute(logger, "Thing");
assertCalled(mbeanLogIH, "getAttribute", disney);
ccmbs.getAttributes(logger, new String[] {"Thing", "Context"});
assertCalled(mbeanLogIH, "getAttributes", disney);
ccmbs.setAttribute(logger, new Attribute("Thing", "bar"));
assertCalled(mbeanLogIH, "setAttribute", disney);
ccmbs.setAttributes(logger, new AttributeList(
Arrays.asList(new Attribute("Thing", "baz"))));
assertCalled(mbeanLogIH, "setAttributes", disney);
ccmbs.getMBeanInfo(logger);
assertCalled(mbeanLogIH, "getMBeanInfo", disney);
Set<ObjectName> names = ccmbs.queryNames(null, null);
Set<ObjectName> expectedNames = new HashSet<ObjectName>(
Collections.singleton(MBeanServerDelegate.DELEGATE_NAME));
assertEquals("context namespace query includes expected names",
true, names.containsAll(expectedNames));
Set<ObjectName> nsNames = ccmbs.queryNames(new ObjectName("*//:*"), null);
Set<ObjectName> expectedNsNames = new HashSet<ObjectName>(
Arrays.asList(
new ObjectName(ClientContext.NAMESPACE +
ObjectName.NAMESPACE_SEPARATOR + ":" +
JMXNamespace.TYPE_ASSIGNMENT)));
assertEquals("context namespace query includes namespace MBean",
true, nsNames.containsAll(expectedNsNames));
Set<ObjectInstance> insts = ccmbs.queryMBeans(
MBeanServerDelegate.DELEGATE_NAME, null);
assertEquals("size of set from MBeanServerDelegate query", 1, insts.size());
assertEquals("ObjectName from MBeanServerDelegate query",
MBeanServerDelegate.DELEGATE_NAME,
insts.iterator().next().getObjectName());
ObjectName createdName = new ObjectName("a:type=Created");
ObjectInstance createdOI =
ccmbs.createMBean(ShowContext.class.getName(), createdName);
assertEquals("class name from createMBean",
ShowContext.class.getName(), createdOI.getClassName());
assertEquals("ObjectName from createMBean",
createdName, createdOI.getObjectName());
assertEquals("context within createMBean",
disney, ccmbs.getAttribute(createdName, "CreationContext"));
NotificationListener nothingListener = new NotificationListener() {
public void handleNotification(Notification n, Object h) {}
};
ccmbs.addNotificationListener(createdName, nothingListener, null, null);
ccmbs.removeNotificationListener(createdName, nothingListener, null, null);
ccmbs.addNotificationListener(createdName, nothingListener, null, null);
ccmbs.removeNotificationListener(createdName, nothingListener);
Set<String> expectedOps = new HashSet<String>(Arrays.asList(
"preRegister", "postRegister", "addNotificationListener",
"removeNL1", "removeNL3", "getNotificationInfo"));
assertEquals("operations called on MBean",
expectedOps, ccmbs.getAttribute(createdName, "CalledOps"));
assertEquals("ClassLoader for MBean",
ShowContext.class.getClassLoader(),
ccmbs.getClassLoaderFor(createdName));
assertEquals("isRegistered", true, ccmbs.isRegistered(createdName));
assertEquals("isInstanceOf", true, ccmbs.isInstanceOf(createdName,
ShowContext.class.getName()));
assertEquals("isInstanceOf", false, ccmbs.isInstanceOf(createdName,
DynamicMBean.class.getName()));
ccmbs.unregisterMBean(createdName);
assertEquals("isRegistered after unregister",
false, ccmbs.isRegistered(createdName));
MLet mlet = new MLet();
ObjectName defaultMLetName = new ObjectName("DefaultDomain:type=MLet");
ccmbs.registerMBean(mlet, defaultMLetName);
assertEquals("getClassLoader", mlet, ccmbs.getClassLoader(defaultMLetName));
assertEquals("number of MBean operations", 0, mbeanLogIH.log.size());
// Test that contexts still work when we can't combine two encoded contexts.
// Here, we wrap cmbs (mickey=mouse) so that ccmbs2 (donald=duck) cannot
// see that it already contains a context and therefore cannot combine
// into mickey=mouse;donald=duck. We don't actually use the snoop
// capabilities of the returned object -- we just want an opaque
// MBeanServer wrapper
MBeanServer cmbs2 = newSnoop(MBeanServer.class, new LogIH(cmbs));
MBeanServer ccmbs2 = ClientContext.withContext(cmbs2, "donald", "duck");
assertEquals("context when combination is impossible",
disney, ccmbs2.getAttribute(name, "Context"));
// Test failure cases of ClientContext.encode
final List<Map<String, String>> badEncodeArgs =
Arrays.asList(
null,
Collections.<String,String>singletonMap(null, "foo"),
Collections.<String,String>singletonMap("foo", null));
for (Map<String, String> bad : badEncodeArgs) {
try {
String oops = ClientContext.encode(bad);
failed("ClientContext.encode(" + bad + ") should have failed: "
+ oops);
} catch (Exception e) {
assertEquals("Exception for ClientContext.encode(" + bad + ")",
IllegalArgumentException.class, e.getClass());
}
}
// ADD NEW TESTS HERE ^^^
if (failure != null)
throw new Exception(failure);
}
private static void assertEquals(String what, Object x, Object y) {
if (!equal(x, y))
failed(what + ": expected " + string(x) + "; got " + string(y));
}
private static boolean equal(Object x, Object y) {
if (x == y)
return true;
if (x == null || y == null)
return false;
if (x.getClass().isArray())
return Arrays.deepEquals(new Object[] {x}, new Object[] {y});
return x.equals(y);
}
private static String string(Object x) {
String s = Arrays.deepToString(new Object[] {x});
return s.substring(1, s.length() - 1);
}
private static void assertCalled(
LogIH logIH, String op, Map<String, String> expectedContext) {
assertCalled(logIH, op, expectedContext, (Object[]) null);
}
private static void assertCalled(
LogIH logIH, String op, Map<String, String> expectedContext,
Object... params) {
LogRecord lr = logIH.log.remove();
assertEquals("called operation", op, lr.op);
if (params != null)
assertEquals("operation parameters", params, lr.params);
assertEquals("operation context", expectedContext, lr.context);
}
private static void failed(String why) {
failure = why;
new Throwable("FAILED: " + why).printStackTrace(System.out);
}
}

View File

@ -0,0 +1,328 @@
/*
* 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 an MBean can handle localized Notification messages.
* @author Eamonn McManus
*/
import java.util.Collections;
import java.util.ListResourceBundle;
import java.util.Locale;
import java.util.Map;
import java.util.MissingResourceException;
import java.util.ResourceBundle;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;
import javax.management.ClientContext;
import javax.management.JMX;
import javax.management.ListenerNotFoundException;
import javax.management.MBeanNotificationInfo;
import javax.management.MBeanServer;
import javax.management.MBeanServerConnection;
import javax.management.MBeanServerFactory;
import javax.management.Notification;
import javax.management.NotificationBroadcasterSupport;
import javax.management.NotificationEmitter;
import javax.management.NotificationFilter;
import javax.management.NotificationListener;
import javax.management.ObjectName;
import javax.management.SendNotification;
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;
public class LocaleAwareBroadcasterTest {
static final ObjectName mbeanName = ObjectName.valueOf("d:type=LocaleAware");
static final String
messageKey = "broken.window",
defaultMessage = "broken window",
frenchMessage = "fen\u00eatre bris\u00e9e",
irishMessage = "fuinneog briste";
public static class Bundle extends ListResourceBundle {
@Override
protected Object[][] getContents() {
return new Object[][] {
{messageKey, defaultMessage},
};
}
}
public static class Bundle_fr extends ListResourceBundle {
@Override
protected Object[][] getContents() {
return new Object[][] {
{messageKey, frenchMessage},
};
}
}
public static class Bundle_ga extends ListResourceBundle {
@Override
protected Object[][] getContents() {
return new Object[][] {
{messageKey, irishMessage},
};
}
}
static volatile String failure;
public static interface LocaleAwareMBean {
public void sendNotification(Notification n);
}
public static class LocaleAware
implements LocaleAwareMBean, NotificationEmitter, SendNotification {
private final ConcurrentMap<Locale, NotificationBroadcasterSupport>
localeToEmitter = newConcurrentMap();
public void sendNotification(Notification n) {
for (Map.Entry<Locale, NotificationBroadcasterSupport> entry :
localeToEmitter.entrySet()) {
Notification localizedNotif =
localizeNotification(n, entry.getKey());
entry.getValue().sendNotification(localizedNotif);
}
}
public void addNotificationListener(
NotificationListener listener,
NotificationFilter filter,
Object handback)
throws IllegalArgumentException {
Locale locale = ClientContext.getLocale();
NotificationBroadcasterSupport broadcaster;
broadcaster = localeToEmitter.get(locale);
if (broadcaster == null) {
broadcaster = new NotificationBroadcasterSupport();
NotificationBroadcasterSupport old =
localeToEmitter.putIfAbsent(locale, broadcaster);
if (old != null)
broadcaster = old;
}
broadcaster.addNotificationListener(listener, filter, handback);
}
public void removeNotificationListener(NotificationListener listener)
throws ListenerNotFoundException {
Locale locale = ClientContext.getLocale();
NotificationBroadcasterSupport broadcaster =
localeToEmitter.get(locale);
if (broadcaster == null)
throw new ListenerNotFoundException();
broadcaster.removeNotificationListener(listener);
}
public void removeNotificationListener(
NotificationListener listener,
NotificationFilter filter,
Object handback)
throws ListenerNotFoundException {
Locale locale = ClientContext.getLocale();
NotificationBroadcasterSupport broadcaster =
localeToEmitter.get(locale);
if (broadcaster == null)
throw new ListenerNotFoundException();
broadcaster.removeNotificationListener(listener, filter, handback);
}
public MBeanNotificationInfo[] getNotificationInfo() {
return new MBeanNotificationInfo[0];
}
}
// Localize notif using the convention that the message looks like
// [resourcebundlename:resourcekey]defaultmessage
// for example [foo.bar.Resources:unknown.problem]
static Notification localizeNotification(Notification n, Locale locale) {
String msg = n.getMessage();
if (!msg.startsWith("["))
return n;
int close = msg.indexOf(']');
if (close < 0)
throw new IllegalArgumentException("Bad notification message: " + msg);
int colon = msg.indexOf(':');
if (colon < 0 || colon > close)
throw new IllegalArgumentException("Bad notification message: " + msg);
String bundleName = msg.substring(1, colon);
String key = msg.substring(colon + 1, close);
ClassLoader loader = LocaleAwareBroadcasterTest.class.getClassLoader();
ResourceBundle bundle =
ResourceBundle.getBundle(bundleName, locale, loader);
try {
msg = bundle.getString(key);
} catch (MissingResourceException e) {
msg = msg.substring(close + 1);
}
n = (Notification) n.clone();
n.setMessage(msg);
return n;
}
public static void main(String[] args) throws Exception {
Locale.setDefault(new Locale("en"));
testLocal();
testRemote();
if (failure == null)
System.out.println("TEST PASSED");
else
throw new Exception("TEST FAILED: " + failure);
}
static interface AddListenerInLocale {
public void addListenerInLocale(
MBeanServerConnection mbsc,
NotificationListener listener,
Locale locale) throws Exception;
}
private static void testLocal() throws Exception {
System.out.println("Test local MBeanServer using doWithContext");
MBeanServer mbs = makeMBS();
AddListenerInLocale addListener = new AddListenerInLocale() {
public void addListenerInLocale(
final MBeanServerConnection mbsc,
final NotificationListener listener,
Locale locale) throws Exception {
Map<String, String> localeContext = Collections.singletonMap(
ClientContext.LOCALE_KEY, locale.toString());
ClientContext.doWithContext(
localeContext, new Callable<Void>() {
public Void call() throws Exception {
mbsc.addNotificationListener(
mbeanName, listener, null, null);
return null;
}
});
}
};
test(mbs, addListener);
}
private static void testRemote() throws Exception {
System.out.println("Test remote MBeanServer using withLocale");
MBeanServer mbs = makeMBS();
JMXServiceURL url = new JMXServiceURL("service:jmx:rmi://");
JMXConnectorServer cs =
JMXConnectorServerFactory.newJMXConnectorServer(url, null, mbs);
cs.start();
JMXServiceURL addr = cs.getAddress();
JMXConnector cc = JMXConnectorFactory.connect(addr);
MBeanServerConnection mbsc = cc.getMBeanServerConnection();
AddListenerInLocale addListenerInLocale = new AddListenerInLocale() {
public void addListenerInLocale(
MBeanServerConnection mbsc,
NotificationListener listener,
Locale locale) throws Exception {
mbsc = ClientContext.withLocale(mbsc, locale);
mbsc.addNotificationListener(mbeanName, listener, null, null);
}
};
try {
test(mbsc, addListenerInLocale);
} finally {
try {
cc.close();
} catch (Exception e) {}
cs.stop();
}
}
static class QueueListener implements NotificationListener {
final BlockingQueue<Notification> queue =
new ArrayBlockingQueue<Notification>(10);
public void handleNotification(Notification notification,
Object handback) {
queue.add(notification);
}
}
private static void test(
MBeanServerConnection mbsc, AddListenerInLocale addListener)
throws Exception {
QueueListener defaultListener = new QueueListener();
QueueListener frenchListener = new QueueListener();
QueueListener irishListener = new QueueListener();
mbsc.addNotificationListener(mbeanName, defaultListener, null, null);
addListener.addListenerInLocale(mbsc, frenchListener, new Locale("fr"));
addListener.addListenerInLocale(mbsc, irishListener, new Locale("ga"));
LocaleAwareMBean proxy =
JMX.newMBeanProxy(mbsc, mbeanName, LocaleAwareMBean.class);
String notifMsg = "[" + Bundle.class.getName() + ":" + messageKey + "]" +
"broken window (default message that should never be seen)";
Notification notif = new Notification(
"notif.type", mbeanName, 0L, notifMsg);
proxy.sendNotification(notif);
final Object[][] expected = {
{defaultListener, defaultMessage},
{frenchListener, frenchMessage},
{irishListener, irishMessage},
};
for (Object[] exp : expected) {
QueueListener ql = (QueueListener) exp[0];
String msg = (String) exp[1];
System.out.println("Checking: " + msg);
Notification n = ql.queue.poll(1, TimeUnit.SECONDS);
if (n == null)
fail("Did not receive expected notif: " + msg);
if (!n.getMessage().equals(msg)) {
fail("Received notif with wrong message: got " +
n.getMessage() + ", expected " + msg);
}
n = ql.queue.poll(2, TimeUnit.MILLISECONDS);
if (n != null)
fail("Received unexpected extra notif: " + n);
}
}
private static MBeanServer makeMBS() throws Exception {
MBeanServer mbs = MBeanServerFactory.newMBeanServer();
LocaleAware aware = new LocaleAware();
mbs.registerMBean(aware, mbeanName);
return mbs;
}
static <K, V> ConcurrentMap<K, V> newConcurrentMap() {
return new ConcurrentHashMap<K, V>();
}
static void fail(String why) {
System.out.println("FAIL: " + why);
failure = why;
}
}

View File

@ -0,0 +1,140 @@
/*
* Copyright 2007 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 LocaleTest.java
* @bug 5072267
* @summary Test client locales.
* @author Eamonn McManus
*/
import java.lang.management.ManagementFactory;
import java.util.Collections;
import java.util.ListResourceBundle;
import java.util.Locale;
import java.util.Map;
import java.util.ResourceBundle;
import java.util.concurrent.Callable;
import javax.management.ClientContext;
import java.util.Arrays;
import javax.management.MBeanServer;
import javax.management.ObjectName;
public class LocaleTest {
private static String failure;
public static void main(String[] args) throws Exception {
// Test the translation String -> Locale
Locale[] locales = Locale.getAvailableLocales();
System.out.println("Testing String->Locale for " + locales.length +
" locales");
for (Locale loc : locales) {
Map<String, String> ctx = Collections.singletonMap(
ClientContext.LOCALE_KEY, loc.toString());
Locale loc2 = ClientContext.doWithContext(
ctx, new Callable<Locale>() {
public Locale call() {
return ClientContext.getLocale();
}
});
assertEquals(loc, loc2);
}
// Test that a locale-sensitive attribute works
MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
mbs = ClientContext.newContextForwarder(mbs, null);
ObjectName name = new ObjectName("a:type=LocaleSensitive");
mbs.registerMBean(new LocaleSensitive(), name);
Locale.setDefault(Locale.US);
assertEquals("spectacular failure",
mbs.getAttribute(name, "LastProblemDescription"));
MBeanServer frmbs = ClientContext.withContext(
mbs, ClientContext.LOCALE_KEY, Locale.FRANCE.toString());
assertEquals("\u00e9chec r\u00e9tentissant",
frmbs.getAttribute(name, "LastProblemDescription"));
if (failure == null)
System.out.println("TEST PASSED");
else
throw new Exception("TEST FAILED: " + failure);
}
public static interface LocaleSensitiveMBean {
public String getLastProblemDescription();
}
public static class LocaleSensitive implements LocaleSensitiveMBean {
public String getLastProblemDescription() {
Locale loc = ClientContext.getLocale();
ResourceBundle rb = ResourceBundle.getBundle(
MyResources.class.getName(), loc);
return rb.getString("spectacular");
}
}
public static class MyResources extends ListResourceBundle {
protected Object[][] getContents() {
return new Object[][] {
{"spectacular", "spectacular failure"},
};
}
}
public static class MyResources_fr extends ListResourceBundle {
protected Object[][] getContents() {
return new Object[][] {
{"spectacular", "\u00e9chec r\u00e9tentissant"},
};
}
}
private static void assertEquals(Object x, Object y) {
if (!equal(x, y))
failed("expected " + string(x) + "; got " + string(y));
}
private static boolean equal(Object x, Object y) {
if (x == y)
return true;
if (x == null || y == null)
return false;
if (x.getClass().isArray())
return Arrays.deepEquals(new Object[] {x}, new Object[] {y});
return x.equals(y);
}
private static String string(Object x) {
String s = Arrays.deepToString(new Object[] {x});
return s.substring(1, s.length() - 1);
}
private static void failed(String why) {
failure = why;
new Throwable("FAILED: " + why).printStackTrace(System.out);
}
}

View File

@ -0,0 +1,192 @@
/*
* Copyright 2007 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 LocalizableTest
* @bug 5072267 6635499
* @summary Test localizable MBeanInfo using LocalizableMBeanFactory.
* @author Eamonn McManus
*/
import java.lang.management.ManagementFactory;
import java.util.Locale;
import java.util.ResourceBundle;
import javax.management.ClientContext;
import javax.management.Description;
import javax.management.JMX;
import javax.management.MBeanAttributeInfo;
import javax.management.MBeanConstructorInfo;
import javax.management.MBeanInfo;
import javax.management.MBeanOperationInfo;
import javax.management.MBeanParameterInfo;
import javax.management.MBeanServer;
import javax.management.ObjectName;
import localizable.MBeanDescriptions_fr;
import localizable.Whatsit;
import static localizable.WhatsitMBean.*;
public class LocalizableTest {
// If you change the order of the array elements or their number then
// you must also change these constants.
private static final int
MBEAN = 0, ATTR = 1, OPER = 2, PARAM = 3, CONSTR = 4,
CONSTR_PARAM = 5;
private static final String[] englishDescriptions = {
englishMBeanDescription, englishAttrDescription, englishOperDescription,
englishParamDescription, englishConstrDescription,
englishConstrParamDescription,
};
private static final String[] defaultDescriptions = englishDescriptions.clone();
static {
defaultDescriptions[MBEAN] = defaultMBeanDescription;
}
private static final String[] frenchDescriptions = {
frenchMBeanDescription, frenchAttrDescription, frenchOperDescription,
frenchParamDescription, frenchConstrDescription,
frenchConstrParamDescription,
};
private static String failure;
@Description(unlocalizedMBeanDescription)
public static interface UnlocalizedMBean {}
public static class Unlocalized implements UnlocalizedMBean {}
public static void main(String[] args) throws Exception {
ResourceBundle frenchBundle = new MBeanDescriptions_fr();
// The purpose of the previous line is to force that class to be compiled
// when the test is run so it will be available for reflection.
// Yes, we could do this with a @build tag.
MBeanServer plainMBS = ManagementFactory.getPlatformMBeanServer();
MBeanServer unlocalizedMBS =
ClientContext.newContextForwarder(plainMBS, null);
MBeanServer localizedMBS =
ClientContext.newLocalizeMBeanInfoForwarder(plainMBS);
localizedMBS = ClientContext.newContextForwarder(localizedMBS, null);
ObjectName name = new ObjectName("a:b=c");
Whatsit whatsit = new Whatsit();
Object[][] locales = {
{null, englishDescriptions},
{"en", englishDescriptions},
{"fr", frenchDescriptions},
};
for (Object[] localePair : locales) {
String locale = (String) localePair[0];
String[] localizedDescriptions = (String[]) localePair[1];
System.out.println("===Testing locale " + locale + "===");
for (boolean localized : new boolean[] {false, true}) {
String[] descriptions = localized ?
localizedDescriptions : defaultDescriptions;
MBeanServer mbs = localized ? localizedMBS : unlocalizedMBS;
System.out.println("Testing MBean " + whatsit + " with " +
"localized=" + localized);
mbs.registerMBean(whatsit, name);
System.out.println(mbs.getMBeanInfo(name));
try {
test(mbs, name, locale, descriptions);
} catch (Exception e) {
fail("Caught exception: " + e);
} finally {
mbs.unregisterMBean(name);
}
}
}
System.out.println("===Testing unlocalizable MBean===");
Object mbean = new Unlocalized();
localizedMBS.registerMBean(mbean, name);
try {
MBeanInfo mbi = localizedMBS.getMBeanInfo(name);
assertEquals("MBean description", unlocalizedMBeanDescription,
mbi.getDescription());
} finally {
localizedMBS.unregisterMBean(name);
}
System.out.println("===Testing MBeanInfo.localizeDescriptions===");
plainMBS.registerMBean(whatsit, name);
MBeanInfo mbi = plainMBS.getMBeanInfo(name);
Locale french = new Locale("fr");
mbi = mbi.localizeDescriptions(french, whatsit.getClass().getClassLoader());
checkDescriptions(mbi, frenchDescriptions);
if (failure == null)
System.out.println("TEST PASSED");
else
throw new Exception("TEST FAILED: Last failure: " + failure);
}
private static void test(MBeanServer mbs, ObjectName name, String locale,
String[] expectedDescriptions)
throws Exception {
if (locale != null)
mbs = ClientContext.withLocale(mbs, new Locale(locale));
MBeanInfo mbi = mbs.getMBeanInfo(name);
checkDescriptions(mbi, expectedDescriptions);
}
private static void checkDescriptions(MBeanInfo mbi,
String[] expectedDescriptions) {
assertEquals("MBean description",
expectedDescriptions[MBEAN], mbi.getDescription());
MBeanAttributeInfo mbai = mbi.getAttributes()[0];
assertEquals("Attribute description",
expectedDescriptions[ATTR], mbai.getDescription());
MBeanOperationInfo mboi = mbi.getOperations()[0];
assertEquals("Operation description",
expectedDescriptions[OPER], mboi.getDescription());
MBeanParameterInfo mbpi = mboi.getSignature()[0];
assertEquals("Parameter description",
expectedDescriptions[PARAM], mbpi.getDescription());
MBeanConstructorInfo[] mbcis = mbi.getConstructors();
assertEquals("Number of constructors", 2, mbcis.length);
for (MBeanConstructorInfo mbci : mbcis) {
MBeanParameterInfo[] mbcpis = mbci.getSignature();
String constrName = mbcpis.length + "-arg constructor";
assertEquals(constrName + " description",
expectedDescriptions[CONSTR], mbci.getDescription());
if (mbcpis.length > 0) {
assertEquals(constrName + " parameter description",
expectedDescriptions[CONSTR_PARAM],
mbcpis[0].getDescription());
}
}
}
private static void assertEquals(String what, Object expect, Object actual) {
if (expect.equals(actual))
System.out.println("...OK: " + what + " = " + expect);
else
fail(what + " should be " + expect + ", was " + actual);
}
private static void fail(String why) {
System.out.println("FAIL: " + why);
failure = why;
}
}

View File

@ -0,0 +1,496 @@
/*
* Copyright 2007-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 RemoteContextTest.java
* @bug 5072267
* @summary Test client contexts with namespaces.
* @author Eamonn McManus, Daniel Fuchs
*/
import java.lang.management.ManagementFactory;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.net.URLEncoder;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Map;
import java.util.Queue;
import java.util.Set;
import java.util.concurrent.Callable;
import javax.management.Attribute;
import javax.management.AttributeList;
import javax.management.ClientContext;
import javax.management.DynamicMBean;
import javax.management.JMX;
import javax.management.ListenerNotFoundException;
import javax.management.MBeanNotificationInfo;
import javax.management.MBeanRegistration;
import javax.management.MBeanServer;
import javax.management.MBeanServerConnection;
import javax.management.MBeanServerDelegate;
import javax.management.Notification;
import javax.management.NotificationBroadcasterSupport;
import javax.management.NotificationFilter;
import javax.management.NotificationListener;
import javax.management.ObjectInstance;
import javax.management.ObjectName;
import javax.management.StandardMBean;
import javax.management.loading.MLet;
import javax.management.namespace.JMXNamespaces;
import javax.management.namespace.JMXRemoteNamespace;
import javax.management.namespace.JMXNamespace;
import static java.util.Collections.singletonMap;
import javax.management.MBeanServerFactory;
import javax.management.remote.JMXConnectorServer;
import javax.management.remote.JMXConnectorServerFactory;
import javax.management.remote.JMXServiceURL;
public class RemoteContextTest {
private static String failure;
public static interface ShowContextMBean {
public Map<String, String> getContext();
public Map<String, String> getCreationContext();
public Set<String> getCalledOps();
public String getThing();
public void setThing(String x);
public int add(int x, int y);
}
public static class ShowContext
extends NotificationBroadcasterSupport
implements ShowContextMBean, MBeanRegistration {
private final Map<String, String> creationContext;
private final Set<String> calledOps = new HashSet<String>();
public ShowContext() {
creationContext = getContext();
}
public Map<String, String> getContext() {
return ClientContext.getContext();
}
public Map<String, String> getCreationContext() {
return creationContext;
}
public Set<String> getCalledOps() {
return calledOps;
}
public String getThing() {
return "x";
}
public void setThing(String x) {
}
public int add(int x, int y) {
return x + y;
}
public ObjectName preRegister(MBeanServer server, ObjectName name) {
assertEquals(creationContext, getContext());
calledOps.add("preRegister");
return name;
}
public void postRegister(Boolean registrationDone) {
assertEquals(creationContext, getContext());
calledOps.add("postRegister");
}
// The condition checked here is not guaranteed universally true,
// but is true every time we unregister an instance of this MBean
// in this test.
public void preDeregister() throws Exception {
assertEquals(creationContext, getContext());
}
public void postDeregister() {
assertEquals(creationContext, getContext());
}
// Same remark as for preDeregister
@Override
public MBeanNotificationInfo[] getNotificationInfo() {
calledOps.add("getNotificationInfo");
return super.getNotificationInfo();
}
@Override
public void addNotificationListener(
NotificationListener listener, NotificationFilter filter, Object handback) {
calledOps.add("addNotificationListener");
super.addNotificationListener(listener, filter, handback);
}
@Override
public void removeNotificationListener(
NotificationListener listener)
throws ListenerNotFoundException {
calledOps.add("removeNL1");
super.removeNotificationListener(listener);
}
@Override
public void removeNotificationListener(
NotificationListener listener, NotificationFilter filter, Object handback)
throws ListenerNotFoundException {
calledOps.add("removeNL3");
super.removeNotificationListener(listener, filter, handback);
}
}
private static class LogRecord {
final String op;
final Object[] params;
final Map<String, String> context;
LogRecord(String op, Object[] params, Map<String, String> context) {
this.op = op;
this.params = params;
this.context = context;
}
@Override
public String toString() {
return op + Arrays.deepToString(params) + " " + context;
}
}
private static class LogIH implements InvocationHandler {
private final Object wrapped;
Queue<LogRecord> log = new LinkedList<LogRecord>();
LogIH(Object wrapped) {
this.wrapped = wrapped;
}
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
if (method.getDeclaringClass() != Object.class) {
LogRecord lr =
new LogRecord(
method.getName(), args, ClientContext.getContext());
log.add(lr);
}
try {
return method.invoke(wrapped, args);
} catch (InvocationTargetException e) {
throw e.getCause();
}
}
}
private static <T> T newSnoop(Class<T> wrappedClass, LogIH logIH) {
return wrappedClass.cast(Proxy.newProxyInstance(
wrappedClass.getClassLoader(),
new Class<?>[] {wrappedClass},
logIH));
}
public static void main(String[] args) throws Exception {
final String subnamespace = "sub";
final ObjectName locname = new ObjectName("a:b=c");
final ObjectName name = JMXNamespaces.insertPath(subnamespace,locname);
final MBeanServer mbs = ClientContext.newContextForwarder(
ManagementFactory.getPlatformMBeanServer(), null);
final MBeanServer sub = ClientContext.newContextForwarder(
MBeanServerFactory.newMBeanServer(), null);
final JMXServiceURL anonym = new JMXServiceURL("rmi",null,0);
final Map<String, Object> env = Collections.emptyMap();
final Map<String, String> emptyContext = Collections.emptyMap();
final JMXConnectorServer srv =
JMXConnectorServerFactory.newJMXConnectorServer(anonym,env,sub);
sub.registerMBean(new ShowContext(), locname);
srv.start();
try {
JMXRemoteNamespace subns = JMXRemoteNamespace.
newJMXRemoteNamespace(srv.getAddress(),null);
mbs.registerMBean(subns, JMXNamespaces.getNamespaceObjectName("sub"));
mbs.invoke(JMXNamespaces.getNamespaceObjectName("sub"),
"connect", null,null);
final ShowContextMBean show =
JMX.newMBeanProxy(mbs, name, ShowContextMBean.class);
assertEquals(emptyContext, show.getContext());
ClientContext.doWithContext(singletonMap("foo", "bar"), new Callable<Void>() {
public Void call() {
assertEquals(singletonMap("foo", "bar"), show.getContext());
return null;
}
});
assertEquals(emptyContext, show.getContext());
String got = ClientContext.doWithContext(
singletonMap("foo", "baz"), new Callable<String>() {
public String call() {
return ClientContext.getContext().get("foo");
}
});
assertEquals("baz", got);
Map<String, String> combined = ClientContext.doWithContext(
singletonMap("foo", "baz"), new Callable<Map<String, String>>() {
public Map<String, String> call() throws Exception {
return ClientContext.doWithContext(
singletonMap("fred", "jim"),
new Callable<Map<String, String>>() {
public Map<String, String> call() {
return ClientContext.getContext();
}
});
}
});
assertEquals(singletonMap("fred", "jim"), combined);
final String ugh = "a!?//*=:\"% ";
ClientContext.doWithContext(singletonMap(ugh, ugh), new Callable<Void>() {
public Void call() {
assertEquals(Collections.singletonMap(ugh, ugh),
ClientContext.getContext());
return null;
}
});
// Basic withContext tests
LogIH mbsIH = new LogIH(mbs);
MBeanServer snoopMBS = newSnoop(MBeanServer.class, mbsIH);
MBeanServer ughMBS = ClientContext.withContext(snoopMBS, ugh, ugh);
// ughMBS is never referenced but we check that the withContext call
// included a call to snoopMBS.isRegistered.
String encodedUgh = URLEncoder.encode(ugh, "UTF-8").replace("*", "%2A");
ObjectName expectedName = new ObjectName(
ClientContext.NAMESPACE + ObjectName.NAMESPACE_SEPARATOR +
encodedUgh + "=" + encodedUgh +
ObjectName.NAMESPACE_SEPARATOR + ":" +
JMXNamespace.TYPE_ASSIGNMENT);
assertCalled(mbsIH, "isRegistered", new Object[] {expectedName},
emptyContext);
// Test withDynamicContext
MBeanServerConnection dynamicSnoop =
ClientContext.withDynamicContext(snoopMBS);
assertCalled(mbsIH, "isRegistered",
new Object[] {
JMXNamespaces.getNamespaceObjectName(ClientContext.NAMESPACE)
},
emptyContext);
final ShowContextMBean dynamicShow =
JMX.newMBeanProxy(dynamicSnoop, name, ShowContextMBean.class);
assertEquals(Collections.emptyMap(), dynamicShow.getContext());
assertCalled(mbsIH, "getAttribute", new Object[] {name, "Context"},
emptyContext);
Map<String, String> expectedDynamic =
Collections.singletonMap("gladstone", "gander");
Map<String, String> dynamic = ClientContext.doWithContext(
expectedDynamic,
new Callable<Map<String, String>>() {
public Map<String, String> call() throws Exception {
return dynamicShow.getContext();
}
});
assertEquals(expectedDynamic, dynamic);
ObjectName expectedDynamicName = new ObjectName(
ClientContext.encode(expectedDynamic) +
ObjectName.NAMESPACE_SEPARATOR + name);
assertCalled(mbsIH, "getAttribute",
new Object[] {expectedDynamicName, "Context"}, dynamic);
MBeanServer cmbs = ClientContext.withContext(
mbs, "mickey", "mouse");
ShowContextMBean cshow =
JMX.newMBeanProxy(cmbs, name, ShowContextMBean.class);
assertEquals(Collections.singletonMap("mickey", "mouse"), cshow.getContext());
MBeanServer ccmbs = ClientContext.withContext(
cmbs, "donald", "duck");
ShowContextMBean ccshow =
JMX.newMBeanProxy(ccmbs, name, ShowContextMBean.class);
Map<String, String> disney = new HashMap<String, String>();
disney.put("mickey", "mouse");
disney.put("donald", "duck");
assertEquals(disney, ccshow.getContext());
// Test that all MBS ops produce reasonable results
ObjectName logger = new ObjectName("a:type=Logger");
DynamicMBean showMBean =
new StandardMBean(new ShowContext(), ShowContextMBean.class);
LogIH mbeanLogIH = new LogIH(showMBean);
DynamicMBean logMBean = newSnoop(DynamicMBean.class, mbeanLogIH);
ObjectInstance loggerOI = ccmbs.registerMBean(logMBean, logger);
assertEquals(logger, loggerOI.getObjectName());
// We get a getMBeanInfo call to determine the className in the
// ObjectInstance to return from registerMBean.
assertCalled(mbeanLogIH, "getMBeanInfo", disney);
ccmbs.getAttribute(logger, "Thing");
assertCalled(mbeanLogIH, "getAttribute", disney);
ccmbs.getAttributes(logger, new String[] {"Thing", "Context"});
assertCalled(mbeanLogIH, "getAttributes", disney);
ccmbs.setAttribute(logger, new Attribute("Thing", "bar"));
assertCalled(mbeanLogIH, "setAttribute", disney);
ccmbs.setAttributes(logger, new AttributeList(
Arrays.asList(new Attribute("Thing", "baz"))));
assertCalled(mbeanLogIH, "setAttributes", disney);
ccmbs.getMBeanInfo(logger);
assertCalled(mbeanLogIH, "getMBeanInfo", disney);
Set<ObjectName> names = ccmbs.queryNames(null, null);
Set<ObjectName> expectedNames = new HashSet<ObjectName>(
Collections.singleton(MBeanServerDelegate.DELEGATE_NAME));
expectedNames.removeAll(names);
assertEquals(0, expectedNames.size());
Set<ObjectName> nsNames =
ccmbs.queryNames(new ObjectName("**?*?//:*"), null);
Set<ObjectName> expectedNsNames = new HashSet<ObjectName>(
Arrays.asList(
new ObjectName(ClientContext.NAMESPACE +
ObjectName.NAMESPACE_SEPARATOR + ":" +
JMXNamespace.TYPE_ASSIGNMENT)));
expectedNsNames.removeAll(nsNames);
assertEquals(0, expectedNsNames.size());
Set<ObjectInstance> insts = ccmbs.queryMBeans(
MBeanServerDelegate.DELEGATE_NAME, null);
assertEquals(1, insts.size());
assertEquals(MBeanServerDelegate.DELEGATE_NAME,
insts.iterator().next().getObjectName());
ObjectName createdName = new ObjectName("a:type=Created");
ObjectInstance createdOI =
ccmbs.createMBean(ShowContext.class.getName(), createdName);
assertEquals(ShowContext.class.getName(), createdOI.getClassName());
assertEquals(createdName, createdOI.getObjectName());
assertEquals(disney, ccmbs.getAttribute(createdName, "CreationContext"));
NotificationListener nothingListener = new NotificationListener() {
public void handleNotification(Notification n, Object h) {}
};
ccmbs.addNotificationListener(createdName, nothingListener, null, null);
ccmbs.removeNotificationListener(createdName, nothingListener, null, null);
ccmbs.addNotificationListener(createdName, nothingListener, null, null);
ccmbs.removeNotificationListener(createdName, nothingListener);
Set<String> expectedOps = new HashSet<String>(Arrays.asList(
"preRegister", "postRegister", "addNotificationListener",
"removeNL1", "removeNL3", "getNotificationInfo"));
assertEquals(expectedOps, ccmbs.getAttribute(createdName, "CalledOps"));
assertEquals(ShowContext.class.getClassLoader(),
ccmbs.getClassLoaderFor(createdName));
assertEquals(true, ccmbs.isRegistered(createdName));
assertEquals(true, ccmbs.isInstanceOf(createdName,
ShowContext.class.getName()));
assertEquals(false, ccmbs.isInstanceOf(createdName,
DynamicMBean.class.getName()));
ccmbs.unregisterMBean(createdName);
assertEquals(false, ccmbs.isRegistered(createdName));
MLet mlet = new MLet();
ObjectName defaultMLetName = new ObjectName("DefaultDomain:type=MLet");
ccmbs.registerMBean(mlet, defaultMLetName);
assertEquals(mlet, ccmbs.getClassLoader(defaultMLetName));
assertEquals(0, mbeanLogIH.log.size());
// Test that contexts still work when we can't combine two encoded contexts.
// Here, we wrap cmbs (mickey=mouse) so that ccmbs2 (donald=duck) cannot
// see that it already contains a context and therefore cannot combine
// into mickey=mouse;donald=duck. We don't actually use the snoop
// capabilities of the returned object -- we just want an opaque
// MBeanServer wrapper
MBeanServer cmbs2 = newSnoop(MBeanServer.class, new LogIH(cmbs));
MBeanServer ccmbs2 = ClientContext.withContext(cmbs2, "donald", "duck");
assertEquals(disney, ccmbs2.getAttribute(name, "Context"));
// ADD NEW TESTS HERE ^^^
if (failure != null)
throw new Exception(failure);
} finally {
srv.stop();
}
}
private static void assertEquals(Object x, Object y) {
if (!equal(x, y))
failed("expected " + string(x) + "; got " + string(y));
}
private static boolean equal(Object x, Object y) {
if (x == y)
return true;
if (x == null || y == null)
return false;
if (x.getClass().isArray())
return Arrays.deepEquals(new Object[] {x}, new Object[] {y});
return x.equals(y);
}
private static String string(Object x) {
String s = Arrays.deepToString(new Object[] {x});
return s.substring(1, s.length() - 1);
}
private static void assertCalled(
LogIH logIH, String op, Map<String, String> expectedContext) {
assertCalled(logIH, op, null, expectedContext);
}
private static void assertCalled(
LogIH logIH, String op, Object[] params,
Map<String, String> expectedContext) {
LogRecord lr = logIH.log.remove();
assertEquals(op, lr.op);
if (params != null)
assertEquals(params, lr.params);
assertEquals(expectedContext, lr.context);
}
private static void failed(String why) {
failure = why;
new Throwable("FAILED: " + why).printStackTrace(System.out);
}
}

View File

@ -0,0 +1,9 @@
# 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.

View File

@ -0,0 +1,42 @@
/*
* Copyright 2007 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.
*/
package localizable;
import java.util.ListResourceBundle;
import static localizable.WhatsitMBean.*;
public class MBeanDescriptions_fr extends ListResourceBundle {
@Override
protected Object[][] getContents() {
String constrProp = "WhatsitMBean.constructor." + Whatsit.class.getName();
return new Object[][] {
{"WhatsitMBean.mbean", frenchMBeanDescription},
{"WhatsitMBean.attribute.Whatsit", frenchAttrDescription},
{"WhatsitMBean.operation.frob", frenchOperDescription},
{"WhatsitMBean.operation.frob.p1", frenchParamDescription},
{constrProp, frenchConstrDescription},
{constrProp + ".p1", frenchConstrParamDescription},
};
}
}

View File

@ -0,0 +1,59 @@
/*
* Copyright 2007 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.
*/
package localizable;
import javax.management.Description;
public class Whatsit implements WhatsitMBean {
/**
* Attribute : NewAttribute0
*/
private String newAttribute0;
@Description(englishConstrDescription)
public Whatsit() {}
@Description(englishConstrDescription)
public Whatsit(@Description(englishConstrParamDescription) int type) {}
public String getWhatsit() {
return "whatsit";
}
public void frob(String whatsit) {
}
/**
* Get Tiddly
*/
public String getNewAttribute0() {
return newAttribute0;
}
/**
* Set Tiddly
*/
public void setNewAttribute0(String value) {
newAttribute0 = value;
}
}

View File

@ -0,0 +1,53 @@
/*
* Copyright 2007 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.
*/
package localizable;
import javax.management.Description;
@Description(WhatsitMBean.defaultMBeanDescription)
public interface WhatsitMBean {
public static final String
defaultMBeanDescription = "Default whatsit MBean description",
englishMBeanDescription = "A whatsit",
// Previous description appears in MBeanDescriptions.properties
// so it overrides the @Description when that file is used.
frenchMBeanDescription = "Un bidule",
englishAttrDescription = "The whatsit",
frenchAttrDescription = "Le bidule",
englishOperDescription = "Frob the whatsit",
frenchOperDescription = "Frober le bidule",
englishParamDescription = "The whatsit to frob",
frenchParamDescription = "Le bidule \u00e0 frober",
englishConstrDescription = "Make a whatsit",
frenchConstrDescription = "Fabriquer un bidule",
englishConstrParamDescription = "Type of whatsit to make",
frenchConstrParamDescription = "Type de bidule \u00e0 fabriquer",
unlocalizedMBeanDescription = "Unlocalized MBean";
@Description(englishAttrDescription)
public String getWhatsit();
@Description(englishOperDescription)
public void frob(@Description(englishParamDescription) String whatsit);
}

View File

@ -200,8 +200,7 @@ public class CustomForwarderTest {
public static void main(String[] args) throws Exception { public static void main(String[] args) throws Exception {
MBeanServer mbs = ManagementFactory.getPlatformMBeanServer(); MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
MBeanServerForwarder mbsf = EventClientDelegate.newForwarder(); MBeanServerForwarder mbsf = EventClientDelegate.newForwarder(mbs, null);
mbsf.setMBeanServer(mbs);
mbs = mbsf; mbs = mbsf;
// for 1.5 // for 1.5

View File

@ -65,8 +65,7 @@ public class EventClientExecutorTest {
new NamedThreadFactory("LEASE")); new NamedThreadFactory("LEASE"));
MBeanServer mbs = MBeanServerFactory.newMBeanServer(); MBeanServer mbs = MBeanServerFactory.newMBeanServer();
MBeanServerForwarder mbsf = EventClientDelegate.newForwarder(); MBeanServerForwarder mbsf = EventClientDelegate.newForwarder(mbs, null);
mbsf.setMBeanServer(mbs);
mbs = mbsf; mbs = mbsf;
EventClientDelegateMBean ecd = EventClientDelegate.getProxy(mbs); EventClientDelegateMBean ecd = EventClientDelegate.getProxy(mbs);

View File

@ -98,7 +98,7 @@ public class EventManagerTest {
succeed &= test(new EventClient(ecd, succeed &= test(new EventClient(ecd,
new RMIPushEventRelay(ecd), new RMIPushEventRelay(ecd),
null, null, null, null,
EventClient.DEFAULT_LEASE_TIMEOUT)); EventClient.DEFAULT_REQUESTED_LEASE_TIME));
conn.close(); conn.close();
server.stop(); server.stop();

View File

@ -99,7 +99,7 @@ public class ListenerTest {
succeed &= test(new EventClient(ecd, succeed &= test(new EventClient(ecd,
new RMIPushEventRelay(ecd), new RMIPushEventRelay(ecd),
null, null, null, null,
EventClient.DEFAULT_LEASE_TIMEOUT)); EventClient.DEFAULT_REQUESTED_LEASE_TIME));
conn.close(); conn.close();
server.stop(); server.stop();

View File

@ -95,7 +95,7 @@ public class NotSerializableNotifTest {
FetchingEventRelay.DEFAULT_MAX_NOTIFICATIONS, FetchingEventRelay.DEFAULT_MAX_NOTIFICATIONS,
null); null);
EventClient ec = new EventClient(ecd, eventRelay, null, null, EventClient ec = new EventClient(ecd, eventRelay, null, null,
EventClient.DEFAULT_LEASE_TIMEOUT); EventClient.DEFAULT_REQUESTED_LEASE_TIME);
// add listener from the client side // add listener from the client side
Listener listener = new Listener(); Listener listener = new Listener();

View File

@ -1,3 +1,26 @@
/*
* 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 UsingEventService.java 1.10 08/01/22 * @test UsingEventService.java 1.10 08/01/22
* @bug 5108776 * @bug 5108776

View File

@ -85,6 +85,7 @@ public class EventWithNamespaceControlTest extends EventWithNamespaceTest {
} }
} }
@Override
public Map<String, ?> getServerMap() { public Map<String, ?> getServerMap() {
Map<String, ?> retValue = Collections.emptyMap(); Map<String, ?> retValue = Collections.emptyMap();
return retValue; return retValue;

View File

@ -51,6 +51,7 @@ import javax.management.namespace.JMXDomain;
import javax.management.namespace.JMXNamespace; import javax.management.namespace.JMXNamespace;
import javax.management.namespace.JMXNamespaces; import javax.management.namespace.JMXNamespaces;
import javax.management.remote.JMXConnectorServer; import javax.management.remote.JMXConnectorServer;
import javax.management.ClientContext;
/** /**
* *

View File

@ -42,6 +42,7 @@ import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import javax.management.ClientContext;
import javax.management.JMException; import javax.management.JMException;
import javax.management.MBeanRegistration; import javax.management.MBeanRegistration;
import javax.management.MBeanServer; import javax.management.MBeanServer;
@ -62,11 +63,6 @@ import javax.management.remote.JMXServiceURL;
*/ */
public class JMXNamespaceViewTest { public class JMXNamespaceViewTest {
// TODO: Remove this when contexts are added.
public static class ClientContext {
public final static String NAMESPACE = "jmx.context";
}
/** /**
* Describe the configuration of a namespace * Describe the configuration of a namespace
*/ */

View File

@ -68,13 +68,7 @@ public class JMXRemoteTargetNamespace extends JMXRemoteNamespace {
public JMXRemoteTargetNamespace(JMXServiceURL sourceURL, public JMXRemoteTargetNamespace(JMXServiceURL sourceURL,
Map<String,?> optionsMap, String sourceNamespace) { Map<String,?> optionsMap, String sourceNamespace) {
this(sourceURL,optionsMap,sourceNamespace,false); super(sourceURL, optionsMap);
}
public JMXRemoteTargetNamespace(JMXServiceURL sourceURL,
Map<String,?> optionsMap, String sourceNamespace,
boolean createEventClient) {
super(sourceURL,optionsMap);
this.sourceNamespace = sourceNamespace; this.sourceNamespace = sourceNamespace;
this.createEventClient = createEventClient(optionsMap); this.createEventClient = createEventClient(optionsMap);
} }
@ -92,14 +86,14 @@ public class JMXRemoteTargetNamespace extends JMXRemoteNamespace {
} }
@Override @Override
protected JMXConnector newJMXConnector(JMXServiceURL url, protected MBeanServerConnection getMBeanServerConnection(JMXConnector jmxc)
Map<String, ?> env) throws IOException { throws IOException {
JMXConnector sup = super.newJMXConnector(url, env); MBeanServerConnection mbsc = super.getMBeanServerConnection(jmxc);
if (sourceNamespace == null || "".equals(sourceNamespace)) if (sourceNamespace != null && sourceNamespace.length() > 0)
return sup; mbsc = JMXNamespaces.narrowToNamespace(mbsc, sourceNamespace);
if (createEventClient) if (createEventClient)
sup = EventClient.withEventClient(sup); mbsc = EventClient.getEventClientConnection(mbsc);
return JMXNamespaces.narrowToNamespace(sup, sourceNamespace); return mbsc;
} }

View File

@ -206,18 +206,10 @@ public class NamespaceNotificationsTest {
aconn.addNotificationListener(deep,listener,null,deep); aconn.addNotificationListener(deep,listener,null,deep);
final JMXServiceURL urlx = new JMXServiceURL(url1.toString()); MBeanServerConnection iconn =
System.out.println("conn: "+urlx); JMXNamespaces.narrowToNamespace(aconn, "server1//server1");
final JMXConnector jc2 = JMXNamespaces.narrowToNamespace( MBeanServerConnection bconn =
JMXConnectorFactory.connect(urlx),"server1//server1"); JMXNamespaces.narrowToNamespace(aconn, "server3");
final JMXConnector jc3 = JMXNamespaces.narrowToNamespace(jc2,"server3");
jc3.connect();
System.out.println("JC#3: " +
((jc3 instanceof JMXAddressable)?
((JMXAddressable)jc3).getAddress():
jc3.toString()));
final MBeanServerConnection bconn =
jc3.getMBeanServerConnection();
final ObjectName shallow = final ObjectName shallow =
new ObjectName("bush:"+ new ObjectName("bush:"+
deep.getKeyPropertyListString()); deep.getKeyPropertyListString());

View File

@ -155,7 +155,7 @@ public class NullDomainObjectNameTest {
// namespace. // namespace.
// //
RoutingServerProxy proxy = RoutingServerProxy proxy =
new RoutingServerProxy(sub, "", "faked", false); new RoutingServerProxy(sub, "", "faked", true);
// These should fail because the ObjectName doesn't start // These should fail because the ObjectName doesn't start
// with "faked//" // with "faked//"

View File

@ -162,7 +162,7 @@ public class NullObjectNameTest {
// this case. // this case.
// //
RoutingServerProxy proxy = RoutingServerProxy proxy =
new RoutingServerProxy(sub,"","faked",false); new RoutingServerProxy(sub, "", "faked", true);
final ObjectInstance moi3 = final ObjectInstance moi3 =
proxy.registerMBean(new MyWombat(),null); proxy.registerMBean(new MyWombat(),null);
System.out.println(moi3.getObjectName().toString()+ System.out.println(moi3.getObjectName().toString()+

View File

@ -21,19 +21,19 @@
* have any questions. * have any questions.
*/ */
import javax.management.openmbean.CompositeType;
import javax.management.openmbean.OpenType;
import javax.management.openmbean.SimpleType;
/* /*
* @test * @test
* @bug 6610174 * @bug 6610174
* @summary Test that CompositeDataSupport.toString() represents arrays correctly * @summary Test that CompositeDataSupport.toString() represents arrays correctly
* @author Eamonn McManus * @author Eamonn McManus
*/ */
import javax.management.openmbean.ArrayType; import javax.management.openmbean.ArrayType;
import javax.management.openmbean.CompositeData; import javax.management.openmbean.CompositeData;
import javax.management.openmbean.CompositeDataSupport; import javax.management.openmbean.CompositeDataSupport;
import javax.management.openmbean.CompositeType;
import javax.management.openmbean.OpenType;
import javax.management.openmbean.SimpleType;
public class CompositeDataStringTest { public class CompositeDataStringTest {
public static void main(String[] args) throws Exception { public static void main(String[] args) throws Exception {

View File

@ -86,16 +86,20 @@ public class ForwarderChainTest {
test(cs, mbs); test(cs, mbs);
System.out.println("===Remove any leftover forwarders==="); System.out.println("===Remove any leftover forwarders===");
while (cs.getSystemMBeanServer() instanceof MBeanServerForwarder) { MBeanServerForwarder systemMBSF = cs.getSystemMBeanServerForwarder();
MBeanServerForwarder mbsf = // Real code would just do systemMBSF.setMBeanServer(mbs).
(MBeanServerForwarder) cs.getSystemMBeanServer(); while (true) {
cs.removeMBeanServerForwarder(mbsf); MBeanServer xmbs = systemMBSF.getMBeanServer();
if (!(xmbs instanceof MBeanServerForwarder))
break;
cs.removeMBeanServerForwarder((MBeanServerForwarder) xmbs);
} }
expectChain(cs, "U", mbs); expectChain(cs, "U", mbs);
System.out.println("===Ensure forwarders are called==="); System.out.println("===Ensure forwarders are called===");
cs.setMBeanServerForwarder(forwarders[0]); cs.setMBeanServerForwarder(forwarders[0]);
cs.setSystemMBeanServerForwarder(forwarders[1]); systemMBSF.setMBeanServer(forwarders[1]);
forwarders[1].setMBeanServer(forwarders[0]);
expectChain(cs, "1U0", mbs); expectChain(cs, "1U0", mbs);
cs.start(); cs.start();
if (forwarders[0].defaultDomainCount != 0 || if (forwarders[0].defaultDomainCount != 0 ||
@ -125,8 +129,8 @@ public class ForwarderChainTest {
private static void test(JMXConnectorServer cs, MBeanServer end) { private static void test(JMXConnectorServer cs, MBeanServer end) {
// A newly-created connector server might have system forwarders, // A newly-created connector server might have system forwarders,
// so get rid of those. // so get rid of those.
while (cs.getSystemMBeanServer() != cs.getMBeanServer()) MBeanServerForwarder systemMBSF = cs.getSystemMBeanServerForwarder();
cs.removeMBeanServerForwarder((MBeanServerForwarder) cs.getSystemMBeanServer()); systemMBSF.setMBeanServer(cs.getMBeanServer());
expectChain(cs, "U", end); expectChain(cs, "U", end);
@ -139,7 +143,8 @@ public class ForwarderChainTest {
expectChain(cs, "U10", end); expectChain(cs, "U10", end);
System.out.println("Add a system forwarder"); System.out.println("Add a system forwarder");
cs.setSystemMBeanServerForwarder(forwarders[2]); forwarders[2].setMBeanServer(systemMBSF.getMBeanServer());
systemMBSF.setMBeanServer(forwarders[2]);
expectChain(cs, "2U10", end); expectChain(cs, "2U10", end);
System.out.println("Add another user forwarder"); System.out.println("Add another user forwarder");
@ -147,7 +152,8 @@ public class ForwarderChainTest {
expectChain(cs, "2U310", end); expectChain(cs, "2U310", end);
System.out.println("Add another system forwarder"); System.out.println("Add another system forwarder");
cs.setSystemMBeanServerForwarder(forwarders[4]); forwarders[4].setMBeanServer(systemMBSF.getMBeanServer());
systemMBSF.setMBeanServer(forwarders[4]);
expectChain(cs, "42U310", end); expectChain(cs, "42U310", end);
System.out.println("Remove the first user forwarder"); System.out.println("Remove the first user forwarder");
@ -215,9 +221,8 @@ public class ForwarderChainTest {
} }
case 2: { // add it to the system chain case 2: { // add it to the system chain
System.out.println("Add " + c + " to system chain"); System.out.println("Add " + c + " to system chain");
if (cs.getSystemMBeanServer() == null) mbsf.setMBeanServer(systemMBSF.getMBeanServer());
mbsf.setMBeanServer(null); systemMBSF.setMBeanServer(mbsf);
cs.setSystemMBeanServerForwarder(mbsf);
chain = c + chain; chain = c + chain;
break; break;
} }
@ -240,7 +245,7 @@ public class ForwarderChainTest {
private static void expectChain( private static void expectChain(
JMXConnectorServer cs, String chain, MBeanServer end) { JMXConnectorServer cs, String chain, MBeanServer end) {
System.out.println("...expected chain: " + chain); System.out.println("...expected chain: " + chain);
MBeanServer curr = cs.getSystemMBeanServer(); MBeanServer curr = cs.getSystemMBeanServerForwarder().getMBeanServer();
int i = 0; int i = 0;
while (i < chain.length()) { while (i < chain.length()) {
char c = chain.charAt(i); char c = chain.charAt(i);

View File

@ -26,6 +26,7 @@ import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import javax.management.ClientContext;
import javax.management.MBeanServer; import javax.management.MBeanServer;
import javax.management.event.EventClientDelegate; import javax.management.event.EventClientDelegate;
import javax.management.remote.JMXConnectorServer; import javax.management.remote.JMXConnectorServer;
@ -62,13 +63,23 @@ public class StandardForwardersTest {
public static void main(String[] args) throws Exception { public static void main(String[] args) throws Exception {
MBeanServer mbs = ManagementFactory.getPlatformMBeanServer(); MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
MBeanServerForwarder ctxFwd = ClientContext.newContextForwarder(mbs, null);
Forwarder ctx = new Forwarder(
JMXConnectorServer.CONTEXT_FORWARDER, true, ctxFwd.getClass());
MBeanServerForwarder locFwd =
ClientContext.newLocalizeMBeanInfoForwarder(mbs);
Forwarder loc = new Forwarder(
JMXConnectorServer.LOCALIZE_MBEAN_INFO_FORWARDER, false,
locFwd.getClass());
MBeanServerForwarder ecdFwd = MBeanServerForwarder ecdFwd =
EventClientDelegate.newForwarder(); EventClientDelegate.newForwarder(mbs, null);
Forwarder ecd = new Forwarder( Forwarder ecd = new Forwarder(
JMXConnectorServer.EVENT_CLIENT_DELEGATE_FORWARDER, true, JMXConnectorServer.EVENT_CLIENT_DELEGATE_FORWARDER, true,
ecdFwd.getClass()); ecdFwd.getClass());
Forwarder[] forwarders = {ecd}; Forwarder[] forwarders = {ctx, loc, ecd};
// Now go through every combination of forwarders. Each forwarder // Now go through every combination of forwarders. Each forwarder
// may be explicitly enabled, explicitly disabled, or left to its // may be explicitly enabled, explicitly disabled, or left to its
@ -154,9 +165,11 @@ public class StandardForwardersTest {
} }
MBeanServer stop = cs.getMBeanServer(); MBeanServer stop = cs.getMBeanServer();
List<Class<?>> foundClasses = new ArrayList<Class<?>>(); List<Class<?>> foundClasses = new ArrayList<Class<?>>();
for (MBeanServer mbs = cs.getSystemMBeanServer(); mbs != stop; for (MBeanServer mbs = cs.getSystemMBeanServerForwarder().getMBeanServer();
mbs = ((MBeanServerForwarder) mbs).getMBeanServer()) mbs != stop;
mbs = ((MBeanServerForwarder) mbs).getMBeanServer()) {
foundClasses.add(mbs.getClass()); foundClasses.add(mbs.getClass());
}
if (!expectedClasses.equals(foundClasses)) { if (!expectedClasses.equals(foundClasses)) {
fail("Incorrect forwarder chain: expected " + expectedClasses + fail("Incorrect forwarder chain: expected " + expectedClasses +
"; found " + foundClasses); "; found " + foundClasses);
@ -165,9 +178,12 @@ public class StandardForwardersTest {
// env is consistent if either (a) localizer is not enabled or (b) // env is consistent if either (a) localizer is not enabled or (b)
// localizer is enabled and context is enabled. // localizer is enabled and context is enabled.
// Neither of those is present in this codebase so env is always consistent.
private static boolean isConsistent(Map<String, String> env) { private static boolean isConsistent(Map<String, String> env) {
return true; String ctxS = env.get(JMXConnectorServer.CONTEXT_FORWARDER);
boolean ctx = (ctxS == null) ? true : Boolean.parseBoolean(ctxS);
String locS = env.get(JMXConnectorServer.LOCALIZE_MBEAN_INFO_FORWARDER);
boolean loc = (locS == null) ? false : Boolean.parseBoolean(locS);
return !loc || ctx;
} }
private static void fail(String why) { private static void fail(String why) {

View File

@ -46,6 +46,8 @@ import javax.management.MBeanServer;
/* /*
* Tests jar services provider are called * Tests jar services provider are called
*/ */
import provider.JMXConnectorProviderImpl;
import provider.JMXConnectorServerProviderImpl;
public class ProviderTest { public class ProviderTest {
public static void main(String[] args) throws Exception { public static void main(String[] args) throws Exception {
System.out.println("Starting ProviderTest"); System.out.println("Starting ProviderTest");
@ -56,8 +58,14 @@ public class ProviderTest {
dotest(url, mbs); dotest(url, mbs);
if(!provider.JMXConnectorProviderImpl.called() || boolean clientCalled = provider.JMXConnectorProviderImpl.called();
!provider.JMXConnectorServerProviderImpl.called()) { boolean serverCalled = provider.JMXConnectorServerProviderImpl.called();
boolean ok = clientCalled && serverCalled;
if (!ok) {
if (!clientCalled)
System.out.println("Client provider not called");
if (!serverCalled)
System.out.println("Server provider not called");
System.out.println("Test Failed"); System.out.println("Test Failed");
System.exit(1); System.exit(1);
} }

View File

@ -75,7 +75,7 @@ public class SimpleStandard
* @return the current value of the "State" attribute. * @return the current value of the "State" attribute.
*/ */
public String getState() { public String getState() {
checkSubject(); checkSubject("getState");
return state; return state;
} }
@ -85,7 +85,7 @@ public class SimpleStandard
* @param <VAR>s</VAR> the new value of the "State" attribute. * @param <VAR>s</VAR> the new value of the "State" attribute.
*/ */
public void setState(String s) { public void setState(String s) {
checkSubject(); checkSubject("setState");
state = s; state = s;
nbChanges++; nbChanges++;
} }
@ -97,7 +97,7 @@ public class SimpleStandard
* @return the current value of the "NbChanges" attribute. * @return the current value of the "NbChanges" attribute.
*/ */
public int getNbChanges() { public int getNbChanges() {
checkSubject(); checkSubject("getNbChanges");
return nbChanges; return nbChanges;
} }
@ -106,7 +106,7 @@ public class SimpleStandard
* attributes of the "SimpleStandard" standard MBean. * attributes of the "SimpleStandard" standard MBean.
*/ */
public void reset() { public void reset() {
checkSubject(); checkSubject("reset");
AttributeChangeNotification acn = AttributeChangeNotification acn =
new AttributeChangeNotification(this, new AttributeChangeNotification(this,
0, 0,
@ -149,18 +149,18 @@ public class SimpleStandard
* Check that the principal contained in the Subject is of * Check that the principal contained in the Subject is of
* type JMXPrincipal and refers to the principalName identity. * type JMXPrincipal and refers to the principalName identity.
*/ */
private void checkSubject() { private void checkSubject(String op) {
AccessControlContext acc = AccessController.getContext(); AccessControlContext acc = AccessController.getContext();
Subject subject = Subject.getSubject(acc); Subject subject = Subject.getSubject(acc);
Set principals = subject.getPrincipals(); Set principals = subject.getPrincipals();
Principal principal = (Principal) principals.iterator().next(); Principal principal = (Principal) principals.iterator().next();
if (!(principal instanceof JMXPrincipal)) if (!(principal instanceof JMXPrincipal))
throw new SecurityException("Authenticated subject contains " + throw new SecurityException(op+": Authenticated subject contains " +
"invalid principal type = " + "invalid principal type = " +
principal.getClass().getName()); principal.getClass().getName());
String identity = principal.getName(); String identity = principal.getName();
if (!identity.equals(principalName)) if (!identity.equals(principalName))
throw new SecurityException("Authenticated subject contains " + throw new SecurityException(op+": Authenticated subject contains " +
"invalid principal name = " + identity); "invalid principal name = " + identity);
} }