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.
* <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.

View File

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

View File

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

View File

@ -55,9 +55,19 @@ import javax.management.namespace.JMXNamespaces;
import javax.management.namespace.MBeanServerSupport;
import javax.management.remote.IdentityMBeanServerForwarder;
/**
* <p>An {@link MBeanServerForwarder} that simulates the existence of a
* given MBean. Requests for that MBean, call it X, are intercepted by the
* forwarder, and requests for any other MBean are forwarded to the next
* forwarder in the chain. Requests such as queryNames which can span both the
* X and other MBeans are handled by merging the results for X with the results
* from the next forwarder, unless the "visible" parameter is false, in which
* case X is invisible to such requests.</p>
*/
public class SingleMBeanForwarder extends IdentityMBeanServerForwarder {
private final ObjectName mbeanName;
private final boolean visible;
private DynamicMBean mbean;
private MBeanServer mbeanMBS = new MBeanServerSupport() {
@ -85,10 +95,20 @@ public class SingleMBeanForwarder extends IdentityMBeanServerForwarder {
return null;
}
// This will only be called if mbeanName has an empty domain.
// In that case a getAttribute (e.g.) of that name will have the
// domain replaced by MBeanServerSupport with the default domain,
// so we must be sure that the default domain is empty too.
@Override
public String getDefaultDomain() {
return mbeanName.getDomain();
}
};
public SingleMBeanForwarder(ObjectName mbeanName, DynamicMBean mbean) {
public SingleMBeanForwarder(
ObjectName mbeanName, DynamicMBean mbean, boolean visible) {
this.mbeanName = mbeanName;
this.visible = visible;
setSingleMBean(mbean);
}
@ -213,8 +233,10 @@ public class SingleMBeanForwarder extends IdentityMBeanServerForwarder {
@Override
public String[] getDomains() {
TreeSet<String> domainSet =
new TreeSet<String>(Arrays.asList(super.getDomains()));
String[] domains = super.getDomains();
if (!visible)
return domains;
TreeSet<String> domainSet = new TreeSet<String>(Arrays.asList(domains));
domainSet.add(mbeanName.getDomain());
return domainSet.toArray(new String[domainSet.size()]);
}
@ -222,7 +244,7 @@ public class SingleMBeanForwarder extends IdentityMBeanServerForwarder {
@Override
public Integer getMBeanCount() {
Integer count = super.getMBeanCount();
if (!super.isRegistered(mbeanName))
if (visible && !super.isRegistered(mbeanName))
count++;
return count;
}
@ -284,7 +306,7 @@ public class SingleMBeanForwarder extends IdentityMBeanServerForwarder {
*/
private boolean applies(ObjectName pattern) {
// we know pattern is not null.
if (!pattern.apply(mbeanName))
if (!visible || !pattern.apply(mbeanName))
return false;
final String dompat = pattern.getDomain();
@ -306,10 +328,12 @@ public class SingleMBeanForwarder extends IdentityMBeanServerForwarder {
@Override
public Set<ObjectInstance> queryMBeans(ObjectName name, QueryExp query) {
Set<ObjectInstance> names = super.queryMBeans(name, query);
if (name == null || applies(name) ) {
// Don't assume mbs.queryNames returns a writable set.
names = Util.cloneSet(names);
names.addAll(mbeanMBS.queryMBeans(name, query));
if (visible) {
if (name == null || applies(name) ) {
// Don't assume mbs.queryNames returns a writable set.
names = Util.cloneSet(names);
names.addAll(mbeanMBS.queryMBeans(name, query));
}
}
return names;
}
@ -317,10 +341,12 @@ public class SingleMBeanForwarder extends IdentityMBeanServerForwarder {
@Override
public Set<ObjectName> queryNames(ObjectName name, QueryExp query) {
Set<ObjectName> names = super.queryNames(name, query);
if (name == null || applies(name)) {
// Don't assume mbs.queryNames returns a writable set.
names = Util.cloneSet(names);
names.addAll(mbeanMBS.queryNames(name, query));
if (visible) {
if (name == null || applies(name)) {
// Don't assume mbs.queryNames returns a writable set.
names = Util.cloneSet(names);
names.addAll(mbeanMBS.queryNames(name, query));
}
}
return names;
}

View File

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

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 boolean identity;
public ObjectNameRouter(String targetDirName) {
this(targetDirName,null);
}
/** Creates a new instance of ObjectNameRouter */
public ObjectNameRouter(final String remove, final String add) {
this.targetPrefix = (remove==null?"":remove);
@ -186,6 +181,4 @@ public class ObjectNameRouter {
b.append(NAMESPACE_SEPARATOR);
return b.toString();
}
}

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

@ -25,6 +25,7 @@
package javax.management;
import com.sun.jmx.mbeanserver.Util;
import java.io.IOException;
import java.io.StreamCorruptedException;
import java.io.Serializable;
@ -37,6 +38,12 @@ import java.util.WeakHashMap;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.HashMap;
import java.util.Locale;
import java.util.MissingResourceException;
import java.util.ResourceBundle;
import java.util.logging.Level;
import java.util.logging.Logger;
import static javax.management.ImmutableDescriptor.nonNullDescriptor;
/**
@ -290,6 +297,7 @@ public class MBeanInfo implements Cloneable, Serializable, DescriptorRead {
* <p>Since this class is immutable, the clone method is chiefly of
* interest to subclasses.</p>
*/
@Override
public Object clone () {
try {
return super.clone() ;
@ -474,6 +482,7 @@ public class MBeanInfo implements Cloneable, Serializable, DescriptorRead {
return (Descriptor) nonNullDescriptor(descriptor).clone();
}
@Override
public String toString() {
return
getClass().getName() + "[" +
@ -505,6 +514,7 @@ public class MBeanInfo implements Cloneable, Serializable, DescriptorRead {
* @return true if and only if <code>o</code> is an MBeanInfo that is equal
* to this one according to the rules above.
*/
@Override
public boolean equals(Object o) {
if (o == this)
return true;
@ -524,6 +534,7 @@ public class MBeanInfo implements Cloneable, Serializable, DescriptorRead {
Arrays.equals(p.fastGetNotifications(), fastGetNotifications()));
}
@Override
public int hashCode() {
/* Since computing the hashCode is quite expensive, we cache it.
If by some terrible misfortune the computed value is 0, the
@ -747,4 +758,377 @@ public class MBeanInfo implements Cloneable, Serializable, DescriptorRead {
throw new StreamCorruptedException("Got unexpected byte.");
}
}
/**
* <p>Return an {@code MBeanInfo} object that is the same as this one
* except that its descriptions are localized in the given locale.
* This means the text returned by {@link MBeanInfo#getDescription}
* (the description of the MBean itself), and the text returned by the
* {@link MBeanFeatureInfo#getDescription getDescription()} method
* for every {@linkplain MBeanAttributeInfo attribute}, {@linkplain
* MBeanOperationInfo operation}, {@linkplain MBeanConstructorInfo
* constructor}, and {@linkplain MBeanNotificationInfo notification}
* contained in the {@code MBeanInfo}.</p>
*
* <p>Here is how the description {@code this.getDescription()} is
* localized.</p>
*
* <p>First, if the {@linkplain #getDescriptor() descriptor}
* of this {@code MBeanInfo} contains a field <code><a
* href="Descriptor.html#locale">"locale"</a></code>, and the value of
* the field is the same as {@code locale.toString()}, then this {@code
* MBeanInfo} is returned. Otherwise, localization proceeds as follows,
* and the {@code "locale"} field in the returned {@code MBeanInfo} will
* be {@code locale.toString()}.
*
* <p>A <em>{@code className}</em> is determined. If this
* {@code MBeanInfo} contains a descriptor with the field
* <a href="Descriptor.html#interfaceClassName">{@code
* "interfaceClassName"}</a>, then the value of that field is the
* {@code className}. Otherwise, it is {@link #getClassName()}.
* Everything before the last period (.) in the {@code className} is
* the <em>{@code package}</em>, and everything after is the <em>{@code
* simpleClassName}</em>. (If there is no period, then the {@code package}
* is empty and the {@code simpleClassName} is the same as the {@code
* className}.)</p>
*
* <p>A <em>{@code resourceKey}</em> is determined. If this {@code
* MBeanInfo} contains a {@linkplain MBeanInfo#getDescriptor() descriptor}
* with a field {@link JMX#DESCRIPTION_RESOURCE_KEY_FIELD
* "descriptionResourceKey"}, the value of the field is
* the {@code resourceKey}. Otherwise, the {@code resourceKey} is {@code
* simpleClassName + ".mbean"}.</p>
*
* <p>A <em>{@code resourceBundleBaseName}</em> is determined. If
* this {@code MBeanInfo} contains a descriptor with a field {@link
* JMX#DESCRIPTION_RESOURCE_BUNDLE_BASE_NAME_FIELD
* "descriptionResourceBundleBaseName"}, the value of the field
* is the {@code resourceBundleBaseName}. Otherwise, the {@code
* resourceBundleBaseName} is {@code package + ".MBeanDescriptions"}.
*
* <p>Then, a {@link java.util.ResourceBundle ResourceBundle} is
* determined, using<br> {@link java.util.ResourceBundle#getBundle(String,
* Locale, ClassLoader) ResourceBundle.getBundle(resourceBundleBaseName,
* locale, loader)}. If this succeeds, and if {@link
* java.util.ResourceBundle#getString(String) getString(resourceKey)}
* returns a string, then that string is the localized description.
* Otherwise, the original description is unchanged.</p>
*
* <p>A localized description for an {@code MBeanAttributeInfo} is
* obtained similarly. The default {@code resourceBundleBaseName}
* is the same as above. The default description and the
* descriptor fields {@code "descriptionResourceKey"} and {@code
* "descriptionResourceBundleBaseName"} come from the {@code
* MBeanAttributeInfo} rather than the {@code MBeanInfo}. If the
* attribute's {@linkplain MBeanFeatureInfo#getName() name} is {@code
* Foo} then its default {@code resourceKey} is {@code simpleClassName +
* ".attribute.Foo"}.</p>
*
* <p>Similar rules apply for operations, constructors, and notifications.
* If the name of the operation, constructor, or notification is {@code
* Foo} then the default {@code resourceKey} is respectively {@code
* simpleClassName + ".operation.Foo"}, {@code simpleClassName +
* ".constructor.Foo"}, or {@code simpleClassName + ".notification.Foo"}.
* If two operations or constructors have the same name (overloading) then
* they have the same default {@code resourceKey}; if different localized
* descriptions are needed then a non-default key must be supplied using
* {@code "descriptionResourceKey"}.</p>
*
* <p>Similar rules also apply for descriptions of parameters ({@link
* MBeanParameterInfo}). The default {@code resourceKey} for a parameter
* whose {@linkplain MBeanFeatureInfo#getName() name} is {@code
* Bar} in an operation or constructor called {@code Foo} is {@code
* simpleClassName + ".operation.Foo.Bar"} or {@code simpleClassName +
* ".constructor.Foo.Bar"} respectively.</p>
*
* <h4>Example</h4>
*
* <p>Suppose you have an MBean defined by these two Java source files:</p>
*
* <pre>
* // ConfigurationMBean.java
* package com.example;
* public interface ConfigurationMBean {
* public String getName();
* public void save(String fileName);
* }
*
* // Configuration.java
* package com.example;
* public class Configuration implements ConfigurationMBean {
* public Configuration(String defaultName) {
* ...
* }
* ...
* }
* </pre>
*
* <p>Then you could define the default descriptions for the MBean, by
* including a resource bundle called {@code com/example/MBeanDescriptions}
* with the compiled classes. Most often this is done by creating a file
* {@code MBeanDescriptions.properties} in the same directory as {@code
* ConfigurationMBean.java}. Make sure that this file is copied into the
* same place as the compiled classes; in typical build environments that
* will be true by default.</p>
*
* <p>The file {@code com/example/MBeanDescriptions.properties} might
* look like this:</p>
*
* <pre>
* # Description of the MBean
* ConfigurationMBean.mbean = Configuration manager
*
* # Description of the Name attribute
* ConfigurationMBean.attribute.Name = The name of the configuration
*
* # Description of the save operation
* ConfigurationMBean.operation.save = Save the configuration to a file
*
* # Description of the parameter to the save operation.
* # Parameter names from the original Java source are not available,
* # so the default names are p1, p2, etc. If the names were available,
* # this would be ConfigurationMBean.operation.save.fileName
* ConfigurationMBean.operation.save.p1 = The name of the file
*
* # Description of the constructor. The default name of a constructor is
* # its fully-qualified class name.
* ConfigurationMBean.constructor.com.example.Configuration = <!--
* -->Constructor with name of default file
* # Description of the constructor parameter.
* ConfigurationMBean.constructor.com.example.Configuration.p1 = <!--
* -->Name of the default file
* </pre>
*
* <p>Starting with this file, you could create descriptions for the French
* locale by creating {@code com/example/MBeanDescriptions_fr.properties}.
* The keys in this file are the same as before but the text has been
* translated:
*
* <pre>
* ConfigurationMBean.mbean = Gestionnaire de configuration
*
* ConfigurationMBean.attribute.Name = Le nom de la configuration
*
* ConfigurationMBean.operation.save = Sauvegarder la configuration <!--
* -->dans un fichier
*
* ConfigurationMBean.operation.save.p1 = Le nom du fichier
*
* ConfigurationMBean.constructor.com.example.Configuration = <!--
* -->Constructeur avec nom du fichier par d&eacute;faut
* ConfigurationMBean.constructor.com.example.Configuration.p1 = <!--
* -->Nom du fichier par d&eacute;faut
* </pre>
*
* <p>The descriptions in {@code MBeanDescriptions.properties} and
* {@code MBeanDescriptions_fr.properties} will only be consulted if
* {@code localizeDescriptions} is called, perhaps because the
* MBean Server has been wrapped by {@link
* ClientContext#newLocalizeMBeanInfoForwarder} or because the
* connector server has been created with the {@link
* javax.management.remote.JMXConnectorServer#LOCALIZE_MBEAN_INFO_FORWARDER
* LOCALIZE_MBEAN_INFO_FORWARDER} option. If you want descriptions
* even when there is no localization step, then you should consider
* using {@link Description &#64;Description} annotations. Annotations
* provide descriptions by default but are overridden if {@code
* localizeDescriptions} is called.</p>
*
* @param locale the target locale for descriptions. Cannot be null.
*
* @param loader the {@code ClassLoader} to use for looking up resource
* bundles.
*
* @return an {@code MBeanInfo} with descriptions appropriately localized.
*
* @throws NullPointerException if {@code locale} is null.
*/
public MBeanInfo localizeDescriptions(Locale locale, ClassLoader loader) {
if (locale == null)
throw new NullPointerException("locale");
Descriptor d = getDescriptor();
String mbiLocaleString = (String) d.getFieldValue(JMX.LOCALE_FIELD);
if (locale.toString().equals(mbiLocaleString))
return this;
return new Rewriter(this, locale, loader).getMBeanInfo();
}
private static class Rewriter {
private final MBeanInfo mbi;
private final ClassLoader loader;
private final Locale locale;
private final String packageName;
private final String simpleClassNamePlusDot;
private ResourceBundle defaultBundle;
private boolean defaultBundleLoaded;
// ResourceBundle.getBundle throws NullPointerException
// if the loader is null, even though that is perfectly
// valid and means the bootstrap loader. So we work
// around with a ClassLoader that is equivalent to the
// bootstrap loader but is not null.
private static final ClassLoader bootstrapLoader =
new ClassLoader(null) {};
Rewriter(MBeanInfo mbi, Locale locale, ClassLoader loader) {
this.mbi = mbi;
this.locale = locale;
if (loader == null)
loader = bootstrapLoader;
this.loader = loader;
String intfName = (String)
mbi.getDescriptor().getFieldValue("interfaceClassName");
if (intfName == null)
intfName = mbi.getClassName();
int lastDot = intfName.lastIndexOf('.');
this.packageName = intfName.substring(0, lastDot + 1);
this.simpleClassNamePlusDot = intfName.substring(lastDot + 1) + ".";
// Inner classes show up as Outer$Inner so won't match the dot.
// When there is no dot, lastDot is -1,
// packageName is empty, and simpleClassNamePlusDot is intfName.
}
MBeanInfo getMBeanInfo() {
MBeanAttributeInfo[] mbais =
rewrite(mbi.getAttributes(), "attribute.");
MBeanOperationInfo[] mbois =
rewrite(mbi.getOperations(), "operation.");
MBeanConstructorInfo[] mbcis =
rewrite(mbi.getConstructors(), "constructor.");
MBeanNotificationInfo[] mbnis =
rewrite(mbi.getNotifications(), "notification.");
Descriptor d = mbi.getDescriptor();
d = changeLocale(d);
String description = getDescription(d, "mbean", "");
if (description == null)
description = mbi.getDescription();
return new MBeanInfo(
mbi.getClassName(), description,
mbais, mbcis, mbois, mbnis, d);
}
private Descriptor changeLocale(Descriptor d) {
if (d.getFieldValue(JMX.LOCALE_FIELD) != null) {
Map<String, Object> map = new HashMap<String, Object>();
for (String field : d.getFieldNames())
map.put(field, d.getFieldValue(field));
map.remove(JMX.LOCALE_FIELD);
d = new ImmutableDescriptor(map);
}
return ImmutableDescriptor.union(
d, new ImmutableDescriptor(JMX.LOCALE_FIELD + "=" + locale));
}
private String getDescription(
Descriptor d, String defaultPrefix, String defaultSuffix) {
ResourceBundle bundle = bundleFromDescriptor(d);
if (bundle == null)
return null;
String key =
(String) d.getFieldValue(JMX.DESCRIPTION_RESOURCE_KEY_FIELD);
if (key == null)
key = simpleClassNamePlusDot + defaultPrefix + defaultSuffix;
return descriptionFromResource(bundle, key);
}
private <T extends MBeanFeatureInfo> T[] rewrite(
T[] features, String resourcePrefix) {
for (int i = 0; i < features.length; i++) {
T feature = features[i];
Descriptor d = feature.getDescriptor();
String description =
getDescription(d, resourcePrefix, feature.getName());
if (description != null &&
!description.equals(feature.getDescription())) {
features[i] = setDescription(feature, description);
}
}
return features;
}
private <T extends MBeanFeatureInfo> T setDescription(
T feature, String description) {
Object newf;
String name = feature.getName();
Descriptor d = feature.getDescriptor();
if (feature instanceof MBeanAttributeInfo) {
MBeanAttributeInfo mbai = (MBeanAttributeInfo) feature;
newf = new MBeanAttributeInfo(
name, mbai.getType(), description,
mbai.isReadable(), mbai.isWritable(), mbai.isIs(),
d);
} else if (feature instanceof MBeanOperationInfo) {
MBeanOperationInfo mboi = (MBeanOperationInfo) feature;
MBeanParameterInfo[] sig = rewrite(
mboi.getSignature(), "operation." + name + ".");
newf = new MBeanOperationInfo(
name, description, sig,
mboi.getReturnType(), mboi.getImpact(), d);
} else if (feature instanceof MBeanConstructorInfo) {
MBeanConstructorInfo mbci = (MBeanConstructorInfo) feature;
MBeanParameterInfo[] sig = rewrite(
mbci.getSignature(), "constructor." + name + ".");
newf = new MBeanConstructorInfo(
name, description, sig, d);
} else if (feature instanceof MBeanNotificationInfo) {
MBeanNotificationInfo mbni = (MBeanNotificationInfo) feature;
newf = new MBeanNotificationInfo(
mbni.getNotifTypes(), name, description, d);
} else if (feature instanceof MBeanParameterInfo) {
MBeanParameterInfo mbpi = (MBeanParameterInfo) feature;
newf = new MBeanParameterInfo(
name, mbpi.getType(), description, d);
} else {
logger().log(Level.FINE, "Unknown feature type: " +
feature.getClass());
newf = feature;
}
return Util.<T>cast(newf);
}
private ResourceBundle bundleFromDescriptor(Descriptor d) {
String bundleName = (String) d.getFieldValue(
JMX.DESCRIPTION_RESOURCE_BUNDLE_BASE_NAME_FIELD);
if (bundleName != null)
return getBundle(bundleName);
if (defaultBundleLoaded)
return defaultBundle;
bundleName = packageName + "MBeanDescriptions";
defaultBundle = getBundle(bundleName);
defaultBundleLoaded = true;
return defaultBundle;
}
private String descriptionFromResource(
ResourceBundle bundle, String key) {
try {
return bundle.getString(key);
} catch (MissingResourceException e) {
logger().log(Level.FINEST, "No resource for " + key, e);
} catch (Exception e) {
logger().log(Level.FINE, "Bad resource for " + key, e);
}
return null;
}
private ResourceBundle getBundle(String name) {
try {
return ResourceBundle.getBundle(name, locale, loader);
} catch (Exception e) {
logger().log(Level.FINE,
"Could not load ResourceBundle " + name, e);
return null;
}
}
private Logger logger() {
return Logger.getLogger("javax.management.locale");
}
}
}

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
* de-registration.
* unregistration.
* <P>
* To receive to MBeanServerNotifications, you need to be declared as listener to
* the {@link javax.management.MBeanServerDelegate javax.management.MBeanServerDelegate} MBean
* that represents the MBeanServer. The ObjectName of the MBeanServerDelegate is:
* To receive MBeanServerNotifications, you need to register a listener with
* the {@link MBeanServerDelegate MBeanServerDelegate} MBean
* that represents the MBeanServer. The ObjectName of the MBeanServerDelegate is
* {@link MBeanServerDelegate#DELEGATE_NAME}, which is
* <CODE>JMImplementation:type=MBeanServerDelegate</CODE>.
*
* <p>The following code prints a message every time an MBean is registered
* or unregistered in the MBean Server {@code mbeanServer}:</p>
*
* <pre>
* private static final NotificationListener printListener = new NotificationListener() {
* public void handleNotification(Notification n, Object handback) {
* if (!(n instanceof MBeanServerNotification)) {
* System.out.println("Ignored notification of class " + n.getClass().getName());
* return;
* }
* MBeanServerNotification mbsn = (MBeanServerNotification) n;
* String what;
* if (n.getType().equals(MBeanServerNotification.REGISTRATION_NOTIFICATION))
* what = "MBean registered";
* else if (n.getType().equals(MBeanServerNotification.UNREGISTRATION_NOTIFICATION))
* what = "MBean unregistered";
* else
* what = "Unknown type " + n.getType();
* System.out.println("Received MBean Server notification: " + what + ": " +
* mbsn.getMBeanName());
* };
*
* ...
* mbeanServer.addNotificationListener(
* MBeanServerDelegate.DELEGATE_NAME, printListener, null, null);
* </pre>
*
* @since 1.5
*/
public class MBeanServerNotification extends Notification {

View File

@ -54,7 +54,7 @@ import com.sun.jmx.mbeanserver.GetPropertyAction;
* @since 1.5
*/
@SuppressWarnings("serial") // serialVersionUID is not constant
public class Notification extends EventObject {
public class Notification extends EventObject implements Cloneable {
// Serialization compatibility stuff:
// Two serial forms are supported in this class. The selected form depends
@ -243,6 +243,26 @@ public class Notification extends EventObject {
this.message = message ;
}
/**
* <p>Creates and returns a copy of this object. The copy is created as
* described for {@link Object#clone()}. This means, first, that the
* class of the object will be the same as the class of this object, and,
* second, that the copy is a "shallow copy". Fields of this notification
* are not themselves copied. In particular, the {@linkplain
* #getUserData user data} of the copy is the same object as the
* original.</p>
*
* @return a copy of this object.
*/
@Override
public Object clone() {
try {
return super.clone();
} catch (CloneNotSupportedException e) {
throw new AssertionError(e);
}
}
/**
* Sets the source.
*
@ -285,8 +305,10 @@ public class Notification extends EventObject {
/**
* Get the notification type.
*
* @return The notification type. It's a string expressed in a dot notation similar
* to Java properties. An example of a notification type is network.alarm.router .
* @return The notification type. It's a string expressed in a dot notation
* similar to Java properties. It is recommended that the notification type
* should follow the reverse-domain-name convention used by Java package
* names. An example of a notification type is com.example.alarm.router.
*/
public String getType() {
return type ;
@ -317,14 +339,25 @@ public class Notification extends EventObject {
/**
* Get the notification message.
*
* @return The message string of this notification object. It contains in a string,
* which could be the explanation of the notification for displaying to a user
* @return The message string of this notification object.
*
* @see #setMessage
*/
public String getMessage() {
return message ;
}
/**
* Set the notification message.
*
* @param message the new notification message.
*
* @see #getMessage
*/
public void setMessage(String message) {
this.message = message;
}
/**
* Get the user data.
*
@ -355,6 +388,7 @@ public class Notification extends EventObject {
*
* @return A String representation of this notification.
*/
@Override
public String toString() {
return super.toString()+"[type="+type+"][message="+message+"]";
}

View File

@ -29,7 +29,6 @@ import com.sun.jmx.event.DaemonThreadFactory;
import com.sun.jmx.event.LeaseRenewer;
import com.sun.jmx.event.ReceiverBuffer;
import com.sun.jmx.event.RepeatedSingletonJob;
import com.sun.jmx.namespace.JMXNamespaceUtils;
import com.sun.jmx.mbeanserver.PerThreadGroupPool;
import com.sun.jmx.remote.util.ClassLogger;
@ -58,7 +57,6 @@ import javax.management.NotificationBroadcasterSupport;
import javax.management.NotificationFilter;
import javax.management.NotificationListener;
import javax.management.ObjectName;
import javax.management.remote.JMXConnector;
import javax.management.remote.NotificationResult;
import javax.management.remote.TargetedNotification;
@ -129,11 +127,12 @@ public class EventClient implements EventConsumer, NotificationManager {
public static final String NOTIFS_LOST = "jmx.event.service.notifs.lost";
/**
* The default lease time, {@value}, in milliseconds.
* The default lease time that EventClient instances will request, in
* milliseconds. This value is {@value}.
*
* @see EventClientDelegateMBean#lease
*/
public static final long DEFAULT_LEASE_TIMEOUT = 300000;
public static final long DEFAULT_REQUESTED_LEASE_TIME = 300000;
/**
* <p>Constructs a default {@code EventClient} object.</p>
@ -173,7 +172,7 @@ public class EventClient implements EventConsumer, NotificationManager {
*/
public EventClient(EventClientDelegateMBean delegate)
throws IOException {
this(delegate, null, null, null, DEFAULT_LEASE_TIMEOUT);
this(delegate, null, null, null, DEFAULT_REQUESTED_LEASE_TIME);
}
/**
@ -196,7 +195,7 @@ public class EventClient implements EventConsumer, NotificationManager {
* If {@code null}, a default scheduler will be used.
* @param requestedLeaseTime The lease time used to keep this client alive
* in the {@link EventClientDelegateMBean}. A value of zero is equivalent
* to the {@linkplain #DEFAULT_LEASE_TIMEOUT default value}.
* to the {@linkplain #DEFAULT_REQUESTED_LEASE_TIME default value}.
*
* @throws IllegalArgumentException If {@code delegate} is null.
* @throws IOException If an I/O error occurs when communicating with the
@ -213,7 +212,7 @@ public class EventClient implements EventConsumer, NotificationManager {
}
if (requestedLeaseTime == 0)
requestedLeaseTime = DEFAULT_LEASE_TIMEOUT;
requestedLeaseTime = DEFAULT_REQUESTED_LEASE_TIME;
else if (requestedLeaseTime < 0) {
throw new IllegalArgumentException(
"Negative lease time: " + requestedLeaseTime);
@ -269,7 +268,13 @@ public class EventClient implements EventConsumer, NotificationManager {
new ScheduledThreadPoolExecutor(20, daemonThreadFactory);
executor.setKeepAliveTime(1, TimeUnit.SECONDS);
executor.allowCoreThreadTimeOut(true);
executor.setRemoveOnCancelPolicy(true);
if (setRemoveOnCancelPolicy != null) {
try {
setRemoveOnCancelPolicy.invoke(executor, true);
} catch (Exception e) {
logger.trace("setRemoveOnCancelPolicy", e);
}
}
// By default, a ScheduledThreadPoolExecutor will keep jobs
// in its queue even after they have been cancelled. They
// will only be removed when their scheduled time arrives.
@ -277,12 +282,25 @@ public class EventClient implements EventConsumer, NotificationManager {
// this EventClient, this can lead to a moderately large number
// of objects remaining referenced until the renewal time
// arrives. Hence the above call, which removes the job from
// the queue as soon as it is cancelled.
// the queue as soon as it is cancelled. Since the call is
// new with JDK 7, we invoke it via reflection to make it
// easier to use this code on JDK 6.
return executor;
}
};
return leaseRenewerThreadPool.getThreadPoolExecutor(create);
}
private static final Method setRemoveOnCancelPolicy;
static {
Method m;
try {
m = ScheduledThreadPoolExecutor.class.getMethod(
"setRemoveOnCancelPolicy", boolean.class);
} catch (Exception e) {
m = null;
}
setRemoveOnCancelPolicy = m;
}
/**
@ -1042,7 +1060,7 @@ public class EventClient implements EventConsumer, NotificationManager {
final public EventClient call() throws Exception {
EventClientDelegateMBean ecd = EventClientDelegate.getProxy(conn);
return new EventClient(ecd, eventRelay, null, null,
DEFAULT_LEASE_TIMEOUT);
DEFAULT_REQUESTED_LEASE_TIME);
}
};
@ -1080,24 +1098,6 @@ public class EventClient implements EventConsumer, NotificationManager {
return clientId;
}
/**
* Returns a JMX Connector that will use an {@link EventClient}
* to subscribe for notifications. If the server doesn't have
* an {@link EventClientDelegateMBean}, then the connector will
* use the legacy notification mechanism instead.
*
* @param wrapped The underlying JMX Connector wrapped by the returned
* connector.
*
* @return A JMX Connector that will uses an {@link EventClient}, if
* available.
*
* @see EventClient#getEventClientConnection(MBeanServerConnection)
*/
public static JMXConnector withEventClient(final JMXConnector wrapped) {
return JMXNamespaceUtils.withEventClient(wrapped);
}
private static final PerThreadGroupPool<ScheduledThreadPoolExecutor>
leaseRenewerThreadPool = PerThreadGroupPool.make();
}

View File

@ -149,6 +149,7 @@ public class EventClientDelegate implements EventClientDelegateMBean {
// of a setMBeanServer on some other forwarder later in the chain.
private static class Forwarder extends SingleMBeanForwarder {
private MBeanServer loopMBS;
private static class UnsupportedInvocationHandler
implements InvocationHandler {
@ -173,7 +174,11 @@ public class EventClientDelegate implements EventClientDelegateMBean {
private volatile boolean madeECD;
Forwarder() {
super(OBJECT_NAME, makeUnsupportedECD());
super(OBJECT_NAME, makeUnsupportedECD(), true);
}
synchronized void setLoopMBS(MBeanServer loopMBS) {
this.loopMBS = loopMBS;
}
@Override
@ -186,7 +191,7 @@ public class EventClientDelegate implements EventClientDelegateMBean {
AccessController.doPrivileged(
new PrivilegedAction<EventClientDelegate>() {
public EventClientDelegate run() {
return getEventClientDelegate(Forwarder.this);
return getEventClientDelegate(loopMBS);
}
});
DynamicMBean mbean = new StandardMBean(
@ -208,11 +213,46 @@ public class EventClientDelegate implements EventClientDelegateMBean {
* that are targeted for that MBean and handles them itself. All other
* requests are forwarded to the next element in the forwarder chain.</p>
*
* @param nextMBS the next {@code MBeanServer} in the chain of forwarders,
* which might be another {@code MBeanServerForwarder} or a plain {@code
* MBeanServer}. This is the object to which {@code MBeanServer} requests
* that do not concern the {@code EventClientDelegateMBean} are sent.
* It will be the value of {@link MBeanServerForwarder#getMBeanServer()
* getMBeanServer()} on the returned object, and can be changed with {@link
* MBeanServerForwarder#setMBeanServer setMBeanServer}. It can be null but
* must be set to a non-null value before any {@code MBeanServer} requests
* arrive.
*
* @param loopMBS the {@code MBeanServer} to which requests from the
* {@code EventClientDelegateMBean} should be sent. For example,
* when you invoke the {@link EventClientDelegateMBean#addListener
* addListener} operation on the {@code EventClientDelegateMBean}, it will
* result in a call to {@link
* MBeanServer#addNotificationListener(ObjectName, NotificationListener,
* NotificationFilter, Object) addNotificationListener} on this object.
* If this parameter is null, then these requests will be sent to the
* newly-created {@code MBeanServerForwarder}. Usually the parameter will
* either be null or will be the result of {@link
* javax.management.remote.JMXConnectorServer#getSystemMBeanServerForwarder()
* getSystemMBeanServerForwarder()} for the connector server in which
* this forwarder will be installed.
*
* @return a new {@code MBeanServerForwarder} that simulates the existence
* of an {@code EventClientDelegateMBean}.
*
* @see javax.management.remote.JMXConnectorServer#installStandardForwarders
*/
public static MBeanServerForwarder newForwarder() {
return new Forwarder();
public static MBeanServerForwarder newForwarder(
MBeanServer nextMBS, MBeanServer loopMBS) {
Forwarder mbsf = new Forwarder();
// We must setLoopMBS before setMBeanServer, because when we
// setMBeanServer that will call getEventClientDelegate(loopMBS).
if (loopMBS == null)
loopMBS = mbsf;
mbsf.setLoopMBS(loopMBS);
if (nextMBS != null)
mbsf.setMBeanServer(nextMBS);
return mbsf;
}
/**
@ -437,10 +477,9 @@ public class EventClientDelegate implements EventClientDelegateMBean {
// private classes
// ------------------------------------
private class ClientInfo {
String clientId;
EventBuffer buffer;
NotificationListener clientListener;
Map<Integer, AddedListener> listenerInfoMap =
final String clientId;
final NotificationListener clientListener;
final Map<Integer, AddedListener> listenerInfoMap =
new HashMap<Integer, AddedListener>();
ClientInfo(String clientId, EventForwarder forwarder) {
@ -703,7 +742,8 @@ public class EventClientDelegate implements EventClientDelegateMBean {
clientInfo = clientInfoMap.get(clientId);
if (clientInfo == null) {
throw new EventClientNotFoundException("The client is not found.");
throw new EventClientNotFoundException(
"Client not found (id " + clientId + ")");
}
return clientInfo;

View File

@ -51,7 +51,8 @@ import javax.management.remote.NotificationResult;
* and the MBean Server, that will intercept accesses to the Event Client
* Delegate MBean and treat them as the real MBean would. This forwarder is
* inserted by default with the standard RMI Connector Server, and can also
* be created explicitly using {@link EventClientDelegate#newForwarder()}.
* be created explicitly using {@link EventClientDelegate#newForwarder
* EventClientDelegate.newForwarder}.
*
* <li><p>A variant on the above is to replace the MBean Server that is
* used locally with a forwarder as described above. Since
@ -61,9 +62,7 @@ import javax.management.remote.NotificationResult;
*
* <pre>
* MBeanServer mbs = ManagementFactory.getPlatformMBeanServer(); // or whatever
* MBeanServerForwarder mbsf = EventClientDelegate.newForwarder();
* mbsf.setMBeanServer(mbs);
* mbs = mbsf;
* mbs = EventClientDelegate.newForwarder(mbs, null);
* // now use mbs just as you did before, but it will have an EventClientDelegate
* </pre>
*

View File

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

View File

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

View File

@ -26,21 +26,19 @@
package javax.management.namespace;
import com.sun.jmx.defaults.JmxProperties;
import com.sun.jmx.namespace.JMXNamespaceUtils;
import com.sun.jmx.namespace.ObjectNameRouter;
import com.sun.jmx.namespace.serial.RewritingProcessor;
import com.sun.jmx.namespace.RoutingConnectionProxy;
import com.sun.jmx.namespace.RoutingServerProxy;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.management.InstanceNotFoundException;
import javax.management.MBeanServer;
import javax.management.MBeanServerConnection;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;
import javax.management.remote.JMXConnector;
/**
* Static constants and utility methods to help work with
@ -68,23 +66,6 @@ public class JMXNamespaces {
NAMESPACE_SEPARATOR.length();
/**
* Returns a connector connected to a sub name space exposed through
* the parent connector.
* @param parent the parent connector.
* @param namespace the {@linkplain javax.management.namespace name space}
* to which the returned connector is
* connected.
* @return A connector connected to a sub name space exposed through
* the parent connector.
**/
public static JMXConnector narrowToNamespace(final JMXConnector parent,
final String namespace)
throws IOException {
return JMXNamespaceUtils.cd(parent,namespace,true);
}
/**
* Creates a new {@code MBeanServerConnection} proxy on a
* {@linkplain javax.management.namespace sub name space}
@ -96,15 +77,18 @@ public class JMXNamespaces {
* name space} in which to narrow.
* @return A new {@code MBeanServerConnection} proxy that shows the content
* of that name space.
* @throws IllegalArgumentException if the name space does not exist, or
* if a proxy for that name space cannot be created.
* @throws IllegalArgumentException if either argument is null,
* or the name space does not exist, or if a proxy for that name space
* cannot be created. The {@linkplain Throwable#getCause() cause} of
* this exception will be an {@link InstanceNotFoundException} if and only
* if the name space is found not to exist.
*/
public static MBeanServerConnection narrowToNamespace(
MBeanServerConnection parent,
String namespace) {
if (LOG.isLoggable(Level.FINER))
LOG.finer("Making MBeanServerConnection for: " +namespace);
return RoutingConnectionProxy.cd(parent,namespace);
return RoutingConnectionProxy.cd(parent, namespace, true);
}
/**
@ -120,13 +104,15 @@ public class JMXNamespaces {
* of that name space.
* @throws IllegalArgumentException if either argument is null,
* or the name space does not exist, or if a proxy for that name space
* cannot be created.
* cannot be created. The {@linkplain Throwable#getCause() cause} of
* this exception will be an {@link InstanceNotFoundException} if and only
* if the name space is found not to exist.
*/
public static MBeanServer narrowToNamespace(MBeanServer parent,
String namespace) {
if (LOG.isLoggable(Level.FINER))
LOG.finer("Making NamespaceServerProxy for: " +namespace);
return RoutingServerProxy.cd(parent,namespace);
LOG.finer("Making MBeanServer for: " +namespace);
return RoutingServerProxy.cd(parent, namespace, true);
}
/**
@ -266,7 +252,7 @@ public class JMXNamespaces {
ObjectNameRouter.normalizeNamespacePath(namespace,false,
true,false);
try {
// We could use Util.newObjectName here - but throwing an
// We could use ObjectName.valueOf here - but throwing an
// IllegalArgumentException that contains just the supplied
// namespace instead of the whole ObjectName seems preferable.
return ObjectName.getInstance(sourcePath+

View File

@ -27,10 +27,10 @@ package javax.management.namespace;
import com.sun.jmx.defaults.JmxProperties;
import com.sun.jmx.mbeanserver.Util;
import com.sun.jmx.namespace.JMXNamespaceUtils;
import com.sun.jmx.remote.util.EnvHelp;
import java.io.IOException;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicLong;
@ -39,6 +39,7 @@ import java.util.logging.Logger;
import javax.management.AttributeChangeNotification;
import javax.management.ClientContext;
import javax.management.InstanceNotFoundException;
import javax.management.ListenerNotFoundException;
import javax.management.MBeanNotificationInfo;
@ -220,17 +221,26 @@ public class JMXRemoteNamespace
initParentOnce(this);
// URL must not be null.
this.jmxURL = JMXNamespaceUtils.checkNonNull(sourceURL,"url");
if (sourceURL == null)
throw new IllegalArgumentException("Null URL");
this.jmxURL = sourceURL;
this.broadcaster =
new NotificationBroadcasterSupport(connectNotification);
// handles options
this.optionsMap = JMXNamespaceUtils.unmodifiableMap(optionsMap);
this.optionsMap = unmodifiableMap(optionsMap);
// handles (dis)connection events
this.listener = new ConnectionListener();
}
// returns un unmodifiable view of a map.
private static <K,V> Map<K,V> unmodifiableMap(Map<K,V> aMap) {
if (aMap == null || aMap.isEmpty())
return Collections.emptyMap();
return Collections.unmodifiableMap(aMap);
}
/**
* Returns the {@code JMXServiceURL} that is (or will be) used to
* connect to the remote name space. <p>
@ -483,106 +493,171 @@ public class JMXRemoteNamespace
}
}
JMXConnector connect(JMXServiceURL url, Map<String,?> env)
private JMXConnector connect(JMXServiceURL url, Map<String,?> env)
throws IOException {
final JMXConnector c = newJMXConnector(jmxURL, env);
final JMXConnector c = newJMXConnector(url, env);
c.connect(env);
return c;
}
/**
* Creates a new JMXConnector with the specified {@code url} and
* {@code env} options map.
* <p>
* This method first calls {@link JMXConnectorFactory#newJMXConnector
* JMXConnectorFactory.newJMXConnector(jmxURL, env)} to obtain a new
* JMX connector, and returns that.
* </p>
* <p>
* A subclass of {@link JMXRemoteNamespace} can provide an implementation
* that connects to a sub namespace of the remote server by subclassing
* this class in the following way:
* <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>
* <p>Creates a new JMXConnector with the specified {@code url} and
* {@code env} options map. The default implementation of this method
* returns {@link JMXConnectorFactory#newJMXConnector
* JMXConnectorFactory.newJMXConnector(jmxURL, env)}. Subclasses can
* override this method to customize behavior.</p>
*
* @param url The JMXServiceURL of the remote server.
* @param optionsMap An unmodifiable options map that will be passed to the
* @param optionsMap An options map that will be passed to the
* {@link JMXConnectorFactory} when {@linkplain
* JMXConnectorFactory#newJMXConnector creating} the
* {@link JMXConnector} that can connect to the remote source
* MBean Server.
* @return An unconnected JMXConnector to use to connect to the remote
* server
* @throws java.io.IOException if the connector could not be created.
* @return A JMXConnector to use to connect to the remote server
* @throws IOException if the connector could not be created.
* @see JMXConnectorFactory#newJMXConnector(javax.management.remote.JMXServiceURL, java.util.Map)
* @see #JMXRemoteNamespace
*/
protected JMXConnector newJMXConnector(JMXServiceURL url,
Map<String,?> optionsMap) throws IOException {
final JMXConnector c =
JMXConnectorFactory.newJMXConnector(jmxURL, optionsMap);
// TODO: uncomment this when contexts are added
// return ClientContext.withDynamicContext(c);
return c;
return JMXConnectorFactory.newJMXConnector(jmxURL, optionsMap);
}
/**
* <p>Called when a new connection is established using {@link #connect}
* so that subclasses can customize the connection. The default
* implementation of this method effectively does the following:</p>
*
* <pre>
* MBeanServerConnection mbsc = {@link JMXConnector#getMBeanServerConnection()
* jmxc.getMBeanServerConnection()};
* try {
* return {@link ClientContext#withDynamicContext
* ClientContext.withDynamicContext(mbsc)};
* } catch (IllegalArgumentException e) {
* return mbsc;
* }
* </pre>
*
* <p>In other words, it arranges for the client context to be forwarded
* to the remote MBean Server if the remote MBean Server supports contexts;
* otherwise it ignores the client context.</p>
*
* <h4>Example: connecting to a remote namespace</h4>
*
* <p>A subclass that wanted to narrow into a namespace of
* the remote MBeanServer might look like this:</p>
*
* <pre>
* class JMXRemoteSubNamespace extends JMXRemoteNamespace {
* private final String subnamespace;
*
* JMXRemoteSubNamespace(
* JMXServiceURL url, Map{@code <String, ?>} env, String subnamespace) {
* super(url, 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 {
LOG.fine("connecting...");
final Map<String,Object> env =
@ -590,7 +665,7 @@ public class JMXRemoteNamespace
try {
// XXX: We should probably document this...
// This allows to specify a loader name - which will be
// retrieved from the paret MBeanServer.
// retrieved from the parent MBeanServer.
defaultClassLoader =
EnvHelp.resolveServerClassLoader(env,getMBeanServer());
} catch (InstanceNotFoundException x) {
@ -604,7 +679,7 @@ public class JMXRemoteNamespace
final JMXConnector aconn = connect(url,env);
final MBeanServerConnection msc;
try {
msc = aconn.getMBeanServerConnection();
msc = getMBeanServerConnection(aconn);
aconn.addConnectionNotificationListener(listener,null,aconn);
} catch (IOException io) {
close(aconn);

View File

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

View File

@ -33,6 +33,7 @@ import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import javax.management.ClientContext;
import javax.management.MBeanInfo; // for javadoc
import javax.management.MBeanNotificationInfo;
import javax.management.MBeanRegistration;
@ -101,6 +102,56 @@ public abstract class JMXConnectorServer
public static final String DELEGATE_TO_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
* 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.
*
* @see #setMBeanServerForwarder
* @see #getSystemMBeanServer
* @see #getSystemMBeanServerForwarder
*/
public synchronized MBeanServer getMBeanServer() {
return userMBeanServer;
@ -176,30 +227,36 @@ public abstract class JMXConnectorServer
* this method, the first occurrence in the chain of an object that is
* {@linkplain Object#equals equal} to {@code mbsf} will have been
* removed.</p>
*
* @param mbsf the forwarder to remove
*
* @throws NoSuchElementException if there is no occurrence of {@code mbsf}
* 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) {
if (mbsf == null)
throw new IllegalArgumentException("Invalid null argument: mbsf");
if (systemMBeanServerForwarder.equals(mbsf))
throw new IllegalArgumentException("Cannot remove system forwarder");
MBeanServerForwarder prev = null;
MBeanServer curr = systemMBeanServer;
while (curr instanceof MBeanServerForwarder && !mbsf.equals(curr)) {
prev = (MBeanServerForwarder) curr;
MBeanServerForwarder prev = systemMBeanServerForwarder;
MBeanServer curr;
while (true) {
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))
throw new NoSuchElementException("MBeanServerForwarder not in chain");
MBeanServerForwarder deleted = (MBeanServerForwarder) curr;
MBeanServer next = deleted.getMBeanServer();
if (prev != null)
prev.setMBeanServer(next);
if (systemMBeanServer == deleted)
systemMBeanServer = next;
if (userMBeanServer == deleted)
MBeanServer next = mbsf.getMBeanServer();
prev.setMBeanServer(next);
if (userMBeanServer == mbsf)
userMBeanServer = next;
}
@ -209,66 +266,63 @@ public abstract class JMXConnectorServer
* the systemMBeanServer and userMBeanServer field declarations.
*/
private void insertUserMBeanServer(MBeanServer mbs) {
MBeanServerForwarder lastSystemMBSF = null;
for (MBeanServer mbsi = systemMBeanServer;
mbsi != userMBeanServer;
mbsi = lastSystemMBSF.getMBeanServer()) {
MBeanServerForwarder lastSystemMBSF = systemMBeanServerForwarder;
while (true) {
MBeanServer mbsi = lastSystemMBSF.getMBeanServer();
if (mbsi == userMBeanServer)
break;
lastSystemMBSF = (MBeanServerForwarder) mbsi;
}
userMBeanServer = mbs;
if (lastSystemMBSF == null)
systemMBeanServer = mbs;
else
lastSystemMBSF.setMBeanServer(mbs);
lastSystemMBSF.setMBeanServer(mbs);
}
/**
* <p>Returns the first item in the chain of system and then user
* forwarders. In the simplest case, a {@code JMXConnectorServer}
* is connected directly to an {@code MBeanServer}. But there can
* also be a chain of {@link MBeanServerForwarder}s between the two.
* This chain consists of two sub-chains: first the <em>system chain</em>
* and then the <em>user chain</em>. Incoming requests are given to the
* first forwarder in the system chain. Each forwarder can handle
* a request itself, or more usually forward it to the next forwarder,
* perhaps with some extra behavior such as logging or security
* checking before or after the forwarding. The last forwarder in
* the system chain is followed by the first forwarder in the user
* chain.</p>
* forwarders. There is a chain of {@link MBeanServerForwarder}s between
* a {@code JMXConnectorServer} and its {@code MBeanServer}. This chain
* consists of two sub-chains: first the <em>system chain</em> and then
* the <em>user chain</em>. Incoming requests are given to the first
* forwarder in the system chain. Each forwarder can handle a request
* itself, or more usually forward it to the next forwarder, perhaps with
* some extra behavior such as logging or security checking before or after
* the forwarding. The last forwarder in the system chain is followed by
* the first forwarder in the user chain.</p>
*
* <p>The <em>system chain</em> is usually
* defined by a connector server based on the environment Map;
* see {@link 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 {@link #setSystemMBeanServerForwarder} or
* {@link #removeMBeanServerForwarder}, but in that case the system
* chain is no longer guaranteed to be correct.</p>
* <p>The object returned by this method is the first forwarder in the
* system chain. For a given {@code JMXConnectorServer}, this method
* always returns the same object, which simply forwards every request
* to the next object in the chain.</p>
*
* <p>Not all connector servers support a system chain of forwarders,
* although the standard {@linkplain
* javax.management.remote.rmi.RMIConnectorServer RMI connector
* 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
* #setMBeanServerForwarder} to insert forwarders 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>
* #setMBeanServerForwarder setMBeanServerForwarder} to insert forwarders
* at the head of the user chain.</p>
*
* <p>This code illustrates how the chains can be traversed:</p>
*
* <pre>
* JMXConnectorServer cs;
* System.out.println("system chain:");
* MBeanServer mbs = cs.getSystemMBeanServer();
* MBeanServer mbs = cs.getSystemMBeanServerForwarder();
* while (true) {
* if (mbs == cs.getMBeanServer())
* System.out.println("user chain:");
@ -281,65 +335,40 @@ public abstract class JMXConnectorServer
* System.out.println("--MBean Server");
* </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.
*
* @see #setSystemMBeanServerForwarder
* @throws UnsupportedOperationException if {@link
* #supportsSystemMBeanServerForwarder} returns false.
*
* @see #supportsSystemMBeanServerForwarder
* @see #setMBeanServerForwarder
*
* @since 1.7
*/
public synchronized MBeanServer getSystemMBeanServer() {
return systemMBeanServer;
}
/**
* <p>Inserts an object that intercepts requests for the MBean server
* that arrive through this connector server. This object will be
* 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;
public MBeanServerForwarder getSystemMBeanServerForwarder() {
if (!supportsSystemMBeanServerForwarder()) {
throw new UnsupportedOperationException(
"System MBeanServerForwarder not supported by this " +
"connector server");
}
return systemMBeanServerForwarder;
}
/**
@ -350,19 +379,13 @@ public abstract class JMXConnectorServer
*
* @return true if this connector server supports the system chain of
* forwarders.
*
* @since 1.7
*/
public boolean supportsSystemMBeanServerForwarder() {
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
* based on the attributes in the given {@code Map}. A connector
@ -374,34 +397,90 @@ public abstract class JMXConnectorServer
* <ul>
*
* <li>If {@link #EVENT_CLIENT_DELEGATE_FORWARDER} is absent, or is
* present with the value {@code "true"}, then a forwarder with the
* functionality of {@link EventClientDelegate#newForwarder} is inserted
* at the start of the system chain.</li>
* present with the value {@code "true"}, then a forwarder
* equivalent to {@link EventClientDelegate#newForwarder
* 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>
*
* <p>For {@code EVENT_CLIENT_DELEGATE_FORWARDER}, if the
* attribute is absent from the {@code Map} and a system property
* of the same name is defined, then the value of the system
* property is used as if it were in the {@code Map}.
* <p>For {@code EVENT_CLIENT_DELEGATE_FORWARDER} and {@code
* CONTEXT_FORWARDER}, if the attribute is absent from the {@code
* Map} and a system property of the same name is defined, then
* 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
* by this method.</p>
*
* @throws UnsupportedOperationException if {@link
* #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) {
mustSupportSystemMBSF();
MBeanServerForwarder sysMBSF = getSystemMBeanServerForwarder();
// Remember that forwarders must be added in reverse order!
boolean ecd = EnvHelp.computeBooleanFromString(
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) {
MBeanServerForwarder mbsf = EventClientDelegate.newForwarder();
setSystemMBeanServerForwarder(mbsf);
MBeanServerForwarder mbsf = EventClientDelegate.newForwarder(
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.
*/
@Override
public MBeanNotificationInfo[] getNotificationInfo() {
final String[] types = {
JMXConnectionNotification.OPENED,
@ -684,30 +764,29 @@ public abstract class JMXConnectorServer
* Fields describing the chains of forwarders (MBeanServerForwarders).
* In the general case, the forwarders look something like this:
*
* systemMBeanServer userMBeanServer
* | |
* v v
* mbsf1 -> mbsf2 -> mbsf3 -> mbsf4 -> mbsf5 -> mbs
* userMBeanServer
* |
* v
* systemMBeanServerForwarder -> mbsf2 -> mbsf3 -> mbsf4 -> mbsf5 -> mbs
*
* Here, each mbsfi is an MBeanServerForwarder, and the arrows
* illustrate its getMBeanServer() method. The last MBeanServerForwarder
* can point to an MBeanServer that is not instanceof MBeanServerForwarder,
* here mbs.
*
* Initially, the chain can be empty if this JMXConnectorServer was
* constructed without an MBeanServer. In this case, both systemMBS
* and userMBS will be null. If there is initially an MBeanServer,
* then both systemMBS and userMBS will point to it.
* The system chain is never empty because it always has at least
* systemMBeanServerForwarder. Initially, the user chain can be empty if
* this JMXConnectorServer was constructed without an MBeanServer. In
* 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
* are forwarders in the system chain (between systemMBS and userMBS in the
* picture above), then the last one must point to the old value of userMBS
* (possibly null). It must be updated to point to the new value. If there
* are no forwarders in the system chain, then systemMBS must be updated to
* the new value of userMBS. The invariant is that starting from systemMBS
* and repeatedly calling MBSF.getMBeanServer() you will end up at
* userMBS. The implication is that you will not see any MBeanServer
* object on the way that is not also an MBeanServerForwarder.
* Whenever userMBS is changed, the system chain must be updated. Before
* the update, the last forwarder in the system chain points to the old
* value of userMBS (possibly null). It must be updated to point to
* the new value. The invariant is that starting from systemMBSF and
* repeatedly calling MBSF.getMBeanServer() you will end up at 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
* and adjust the system chain appropriately.
@ -716,7 +795,7 @@ public abstract class JMXConnectorServer
* MBeanServer, then userMBS becomes that MBeanServer, and the system
* 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
* (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
@ -726,7 +805,8 @@ public abstract class JMXConnectorServer
private MBeanServer userMBeanServer;
private MBeanServer systemMBeanServer;
private final MBeanServerForwarder systemMBeanServerForwarder =
new IdentityMBeanServerForwarder();
/**
* 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,
* a system chain and a user chain. See {@link
* JMXConnectorServer#setSystemMBeanServerForwarder} for details.</p>
* JMXConnectorServer#getSystemMBeanServerForwarder} for details.</p>
*
* @param mbsf the new <code>MBeanServerForwarder</code>.
*
@ -141,7 +141,7 @@ public interface JMXConnectorServerMBean {
* with <code>IllegalArgumentException</code>. This includes the
* case where <code>mbsf</code> is null.
*
* @see JMXConnectorServer#setSystemMBeanServerForwarder
* @see JMXConnectorServer#getSystemMBeanServerForwarder
*/
public void setMBeanServerForwarder(MBeanServerForwarder mbsf);

View File

@ -383,7 +383,7 @@ public class RMIConnectorServer extends JMXConnectorServer {
try {
if (tracing) logger.trace("start", "setting default class loader");
defaultClassLoader = EnvHelp.resolveServerClassLoader(
attributes, getSystemMBeanServer());
attributes, getSystemMBeanServerForwarder());
} catch (InstanceNotFoundException infc) {
IllegalArgumentException x = new
IllegalArgumentException("ClassLoader not found: "+infc);
@ -398,7 +398,7 @@ public class RMIConnectorServer extends JMXConnectorServer {
else
rmiServer = newServer();
rmiServer.setMBeanServer(getSystemMBeanServer());
rmiServer.setMBeanServer(getSystemMBeanServerForwarder());
rmiServer.setDefaultClassLoader(defaultClassLoader);
rmiServer.setRMIConnectorServer(this);
rmiServer.export();
@ -592,31 +592,6 @@ public class RMIConnectorServer extends JMXConnectorServer {
return Collections.unmodifiableMap(map);
}
@Override
public synchronized void setMBeanServerForwarder(MBeanServerForwarder mbsf) {
MBeanServer oldSMBS = getSystemMBeanServer();
super.setMBeanServerForwarder(mbsf);
if (oldSMBS != getSystemMBeanServer())
updateMBeanServer();
// If the system chain of MBeanServerForwarders is not empty, then
// there is no need to call rmiServerImpl.setMBeanServer, because
// it is pointing to the head of the system chain and that has not
// changed. (The *end* of the system chain will have been changed
// to point to mbsf.)
}
private void updateMBeanServer() {
if (rmiServerImpl != null)
rmiServerImpl.setMBeanServer(getSystemMBeanServer());
}
@Override
public synchronized void setSystemMBeanServerForwarder(
MBeanServerForwarder mbsf) {
super.setSystemMBeanServerForwarder(mbsf);
updateMBeanServer();
}
/**
* {@inheritDoc}
* @return true, since this connector server does support a system chain
@ -631,16 +606,19 @@ public class RMIConnectorServer extends JMXConnectorServer {
here so that they are accessible to other classes in this package
even though they have protected access. */
@Override
protected void connectionOpened(String connectionId, String message,
Object userData) {
super.connectionOpened(connectionId, message, userData);
}
@Override
protected void connectionClosed(String connectionId, String message,
Object userData) {
super.connectionClosed(connectionId, message, userData);
}
@Override
protected void connectionFailed(String connectionId, String message,
Object userData) {
super.connectionFailed(connectionId, message, userData);

View File

@ -39,7 +39,8 @@ import javax.management.*;
/*
This test checks that annotations produce Descriptor entries as
specified in javax.management.DescriptorKey. It does two things:
specified in javax.management.DescriptorKey and javax.management.DescriptorField.
It does the following:
- An annotation consisting of an int and a String, each with an
appropriate @DescriptorKey annotation, is placed on every program
@ -61,6 +62,10 @@ import javax.management.*;
The test checks that in each case the corresponding Descriptor
appears in the appropriate place inside the MBean's MBeanInfo.
- A @DescriptorFields annotation defining two fields is placed in the
same places and again the test checks that the two fields appear
in the corresponding MBean*Info objects.
- An annotation consisting of enough other types to ensure coverage
is placed on a getter. The test checks that the generated
MBeanAttributeInfo contains the corresponding Descriptor. The tested
@ -78,12 +83,6 @@ import javax.management.*;
public class AnnotationTest {
private static String failed = null;
// @Retention(RetentionPolicy.RUNTIME) @Inherited
// @Target(ElementType.METHOD)
// public static @interface DescriptorKey {
// String value();
// }
@Retention(RetentionPolicy.RUNTIME)
public static @interface Pair {
@DescriptorKey("x")
@ -112,11 +111,12 @@ public class AnnotationTest {
boolean[] booleanArrayValue();
}
/* We use the annotation @Pair(x = 3, y = "foo") everywhere, and this is
the Descriptor that it should produce: */
/* We use the annotations @Pair(x = 3, y = "foo")
and @DescriptorFields({"foo=bar", "baz="}) everywhere, and this is
the Descriptor that they should produce: */
private static Descriptor expectedDescriptor =
new ImmutableDescriptor(new String[] {"x", "y"},
new Object[] {3, "foo"});
new ImmutableDescriptor(new String[] {"x", "y", "foo", "baz"},
new Object[] {3, "foo", "bar", ""});
private static Descriptor expectedFullDescriptor =
new ImmutableDescriptor(new String[] {
@ -136,8 +136,10 @@ public class AnnotationTest {
});
@Pair(x = 3, y = "foo")
@DescriptorFields({"foo=bar", "baz="})
public static interface ThingMBean {
@Pair(x = 3, y = "foo")
@DescriptorFields({"foo=bar", "baz="})
@Full(classValue=Full.class,
enumValue=RetentionPolicy.RUNTIME,
booleanValue=false,
@ -149,32 +151,47 @@ public class AnnotationTest {
int getReadOnly();
@Pair(x = 3, y = "foo")
@DescriptorFields({"foo=bar", "baz="})
void setWriteOnly(int x);
@Pair(x = 3, y = "foo")
@DescriptorFields({"foo=bar", "baz="})
int getReadWrite1();
void setReadWrite1(int x);
@Pair(x = 3, y = "foo")
@DescriptorFields({"foo=bar", "baz="})
int getReadWrite2();
@Pair(x = 3, y = "foo")
@DescriptorFields({"foo=bar", "baz="})
void setReadWrite2(int x);
int getReadWrite3();
@Pair(x = 3, y = "foo")
@DescriptorFields({"foo=bar", "baz="})
void setReadWrite3(int x);
@Pair(x = 3, y = "foo")
int operation(@Pair(x = 3, y = "foo") int p1,
@Pair(x = 3, y = "foo") int p2);
@DescriptorFields({"foo=bar", "baz="})
int operation(@Pair(x = 3, y = "foo")
@DescriptorFields({"foo=bar", "baz="})
int p1,
@Pair(x = 3, y = "foo")
@DescriptorFields({"foo=bar", "baz="})
int p2);
}
public static class Thing implements ThingMBean {
@Pair(x = 3, y = "foo")
@DescriptorFields({"foo=bar", "baz="})
public Thing() {}
@Pair(x = 3, y = "foo")
public Thing(@Pair(x = 3, y = "foo") int p1) {}
@DescriptorFields({"foo=bar", "baz="})
public Thing(
@Pair(x = 3, y = "foo")
@DescriptorFields({"foo=bar", "baz="})
int p1) {}
public int getReadOnly() {return 0;}
@ -193,14 +210,20 @@ public class AnnotationTest {
}
@Pair(x = 3, y = "foo")
@DescriptorFields({"foo=bar", "baz="})
public static interface ThingMXBean extends ThingMBean {}
public static class ThingImpl implements ThingMXBean {
@Pair(x = 3, y = "foo")
@DescriptorFields({"foo=bar", "baz="})
public ThingImpl() {}
@Pair(x = 3, y = "foo")
public ThingImpl(@Pair(x = 3, y = "foo") int p1) {}
@DescriptorFields({"foo=bar", "baz="})
public ThingImpl(
@Pair(x = 3, y = "foo")
@DescriptorFields({"foo=bar", "baz="})
int p1) {}
public int getReadOnly() {return 0;}
@ -218,6 +241,79 @@ public class AnnotationTest {
public int operation(int p1, int p2) {return 0;}
}
@Retention(RetentionPolicy.RUNTIME)
public static @interface DefaultTest {
@DescriptorKey(value = "string1", omitIfDefault = true)
String string1() default "";
@DescriptorKey(value = "string2", omitIfDefault = true)
String string2() default "tiddly pom";
@DescriptorKey(value = "int", omitIfDefault = true)
int intx() default 23;
@DescriptorKey(value = "intarray1", omitIfDefault = true)
int[] intArray1() default {};
@DescriptorKey(value = "intarray2", omitIfDefault = true)
int[] intArray2() default {1, 2};
@DescriptorKey(value = "stringarray1", omitIfDefault = true)
String[] stringArray1() default {};
@DescriptorKey(value = "stringarray2", omitIfDefault = true)
String[] stringArray2() default {"foo", "bar"};
}
@Retention(RetentionPolicy.RUNTIME)
public static @interface Expect {
String[] value() default {};
}
public static interface DefaultMBean {
@DefaultTest
@Expect()
public void a();
@DefaultTest(string1="")
@Expect()
public void b();
@DefaultTest(string1="nondefault")
@Expect("string1=nondefault")
public void c();
@DefaultTest(string2="tiddly pom")
@Expect()
public void d();
@DefaultTest(intx=23)
@Expect()
public void e();
@DefaultTest(intx=34)
@Expect("int=34")
public void f();
@DefaultTest(intArray1={})
@Expect()
public void g();
@DefaultTest(intArray1={2,3})
@Expect("intarray1=[2, 3]")
public void h();
@DefaultTest(intArray2={})
@Expect("intarray2=[]")
public void i();
@DefaultTest(stringArray1={})
@Expect()
public void j();
@DefaultTest(stringArray1={"foo"})
@Expect("stringarray1=[foo]")
public void k();
@DefaultTest(stringArray2={})
@Expect("stringarray2=[]")
public void l();
}
public static void main(String[] args) throws Exception {
System.out.println("Testing that annotations are correctly " +
"reflected in Descriptor entries");
@ -225,20 +321,62 @@ public class AnnotationTest {
MBeanServer mbs =
java.lang.management.ManagementFactory.getPlatformMBeanServer();
ObjectName on = new ObjectName("a:b=c");
Thing thing = new Thing();
mbs.registerMBean(thing, on);
check(mbs, on);
mbs.unregisterMBean(on);
ThingImpl thingImpl = new ThingImpl();
mbs.registerMBean(thingImpl, on);
Descriptor d = mbs.getMBeanInfo(on).getDescriptor();
if (!d.getFieldValue("mxbean").equals("true")) {
System.out.println("NOT OK: expected MXBean");
failed = "Expected MXBean";
}
check(mbs, on);
System.out.println();
System.out.println("Testing that omitIfDefault works");
DefaultMBean defaultImpl = (DefaultMBean) Proxy.newProxyInstance(
DefaultMBean.class.getClassLoader(),
new Class<?>[] {DefaultMBean.class},
new InvocationHandler(){
public Object invoke(Object proxy, Method method, Object[] args) {
return null;
}
});
DynamicMBean mbean = new StandardMBean(defaultImpl, DefaultMBean.class);
MBeanOperationInfo[] ops = mbean.getMBeanInfo().getOperations();
for (MBeanOperationInfo op : ops) {
String name = op.getName();
Expect expect =
DefaultMBean.class.getMethod(name).getAnnotation(Expect.class);
Descriptor opd = op.getDescriptor();
List<String> fields = new ArrayList<String>();
for (String fieldName : opd.getFieldNames()) {
Object value = opd.getFieldValue(fieldName);
String s = Arrays.deepToString(new Object[] {value});
s = s.substring(1, s.length() - 1);
fields.add(fieldName + "=" + s);
}
Descriptor opds = new ImmutableDescriptor(fields.toArray(new String[0]));
Descriptor expd = new ImmutableDescriptor(expect.value());
if (opds.equals(expd))
System.out.println("OK: op " + name + ": " + opds);
else {
String failure = "Bad descriptor for op " + name + ": " +
"expected " + expd + ", got " + opds;
System.out.println("NOT OK: " + failure);
failed = failure;
}
}
System.out.println();
if (failed == null)
System.out.println("Test passed");
else if (true)
throw new Exception("TEST FAILED: " + failed);
else
System.out.println("Test disabled until 6221321 implemented");
throw new Exception("TEST FAILED: " + failed);
}
private static void check(MBeanServer mbs, ObjectName on) throws Exception {
@ -295,151 +433,4 @@ public class AnnotationTest {
for (DescriptorRead x : xx)
check(x);
}
public static class AnnotatedMBean extends StandardMBean {
<T> AnnotatedMBean(T resource, Class<T> interfaceClass, boolean mx) {
super(resource, interfaceClass, mx);
}
private static final String[] attrPrefixes = {"get", "set", "is"};
protected void cacheMBeanInfo(MBeanInfo info) {
MBeanAttributeInfo[] attrs = info.getAttributes();
MBeanOperationInfo[] ops = info.getOperations();
for (int i = 0; i < attrs.length; i++) {
MBeanAttributeInfo attr = attrs[i];
String name = attr.getName();
Descriptor d = attr.getDescriptor();
Method m;
if ((m = getMethod("get" + name)) != null)
d = ImmutableDescriptor.union(d, descriptorFor(m));
if (attr.getType().equals("boolean") &&
(m = getMethod("is" + name)) != null)
d = ImmutableDescriptor.union(d, descriptorFor(m));
if ((m = getMethod("set" + name, attr)) != null)
d = ImmutableDescriptor.union(d, descriptorFor(m));
if (!d.equals(attr.getDescriptor())) {
attrs[i] =
new MBeanAttributeInfo(name, attr.getType(),
attr.getDescription(), attr.isReadable(),
attr.isWritable(), attr.isIs(), d);
}
}
for (int i = 0; i < ops.length; i++) {
MBeanOperationInfo op = ops[i];
String name = op.getName();
Descriptor d = op.getDescriptor();
MBeanParameterInfo[] params = op.getSignature();
Method m = getMethod(name, params);
if (m != null) {
d = ImmutableDescriptor.union(d, descriptorFor(m));
Annotation[][] annots = m.getParameterAnnotations();
for (int pi = 0; pi < params.length; pi++) {
MBeanParameterInfo param = params[pi];
Descriptor pd =
ImmutableDescriptor.union(param.getDescriptor(),
descriptorFor(annots[pi]));
params[pi] = new MBeanParameterInfo(param.getName(),
param.getType(), param.getDescription(), pd);
}
op = new MBeanOperationInfo(op.getName(),
op.getDescription(), params, op.getReturnType(),
op.getImpact(), d);
if (!ops[i].equals(op))
ops[i] = op;
}
}
Descriptor id = descriptorFor(getMBeanInterface());
info = new MBeanInfo(info.getClassName(), info.getDescription(),
attrs, info.getConstructors(), ops, info.getNotifications(),
ImmutableDescriptor.union(id, info.getDescriptor()));
super.cacheMBeanInfo(info);
}
private Descriptor descriptorFor(AnnotatedElement x) {
Annotation[] annots = x.getAnnotations();
return descriptorFor(annots);
}
private Descriptor descriptorFor(Annotation[] annots) {
if (annots.length == 0)
return ImmutableDescriptor.EMPTY_DESCRIPTOR;
Map<String, Object> descriptorMap = new HashMap<String, Object>();
for (Annotation a : annots) {
Class<? extends Annotation> c = a.annotationType();
Method[] elements = c.getMethods();
for (Method element : elements) {
DescriptorKey key =
element.getAnnotation(DescriptorKey.class);
if (key != null) {
String name = key.value();
Object value;
try {
value = element.invoke(a);
} catch (Exception e) {
// we don't expect this
throw new RuntimeException(e);
}
Object oldValue = descriptorMap.put(name, value);
if (oldValue != null && !oldValue.equals(value)) {
final String msg =
"Inconsistent values for descriptor field " +
name + " from annotations: " + value + " :: " +
oldValue;
throw new IllegalArgumentException(msg);
}
}
}
}
if (descriptorMap.isEmpty())
return ImmutableDescriptor.EMPTY_DESCRIPTOR;
else
return new ImmutableDescriptor(descriptorMap);
}
private Method getMethod(String name, MBeanFeatureInfo... params) {
Class<?> intf = getMBeanInterface();
ClassLoader loader = intf.getClassLoader();
Class[] classes = new Class[params.length];
for (int i = 0; i < params.length; i++) {
MBeanFeatureInfo param = params[i];
Descriptor d = param.getDescriptor();
String type = (String) d.getFieldValue("originalType");
if (type == null) {
if (param instanceof MBeanAttributeInfo)
type = ((MBeanAttributeInfo) param).getType();
else
type = ((MBeanParameterInfo) param).getType();
}
Class<?> c = primitives.get(type);
if (c == null) {
try {
c = Class.forName(type, false, loader);
} catch (ClassNotFoundException e) {
return null;
}
}
classes[i] = c;
}
try {
return intf.getMethod(name, classes);
} catch (Exception e) {
return null;
}
}
private static final Map<String, Class<?>> primitives =
new HashMap<String, Class<?>>();
static {
for (Class<?> c :
new Class[] {boolean.class, byte.class, short.class,
int.class, long.class, float.class,
double.class, char.class, void.class}) {
primitives.put(c.getName(), c);
}
}
}
}

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 {
MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
MBeanServerForwarder mbsf = EventClientDelegate.newForwarder();
mbsf.setMBeanServer(mbs);
MBeanServerForwarder mbsf = EventClientDelegate.newForwarder(mbs, null);
mbs = mbsf;
// for 1.5

View File

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

View File

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

View File

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

View File

@ -95,7 +95,7 @@ public class NotSerializableNotifTest {
FetchingEventRelay.DEFAULT_MAX_NOTIFICATIONS,
null);
EventClient ec = new EventClient(ecd, eventRelay, null, null,
EventClient.DEFAULT_LEASE_TIMEOUT);
EventClient.DEFAULT_REQUESTED_LEASE_TIME);
// add listener from the client side
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
* @bug 5108776

View File

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

View File

@ -51,6 +51,7 @@ import javax.management.namespace.JMXDomain;
import javax.management.namespace.JMXNamespace;
import javax.management.namespace.JMXNamespaces;
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.Map;
import java.util.Set;
import javax.management.ClientContext;
import javax.management.JMException;
import javax.management.MBeanRegistration;
import javax.management.MBeanServer;
@ -62,11 +63,6 @@ import javax.management.remote.JMXServiceURL;
*/
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
*/

View File

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

View File

@ -206,18 +206,10 @@ public class NamespaceNotificationsTest {
aconn.addNotificationListener(deep,listener,null,deep);
final JMXServiceURL urlx = new JMXServiceURL(url1.toString());
System.out.println("conn: "+urlx);
final JMXConnector jc2 = JMXNamespaces.narrowToNamespace(
JMXConnectorFactory.connect(urlx),"server1//server1");
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();
MBeanServerConnection iconn =
JMXNamespaces.narrowToNamespace(aconn, "server1//server1");
MBeanServerConnection bconn =
JMXNamespaces.narrowToNamespace(aconn, "server3");
final ObjectName shallow =
new ObjectName("bush:"+
deep.getKeyPropertyListString());

View File

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

View File

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

View File

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

View File

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

View File

@ -26,6 +26,7 @@ import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.management.ClientContext;
import javax.management.MBeanServer;
import javax.management.event.EventClientDelegate;
import javax.management.remote.JMXConnectorServer;
@ -62,13 +63,23 @@ public class StandardForwardersTest {
public static void main(String[] args) throws Exception {
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 =
EventClientDelegate.newForwarder();
EventClientDelegate.newForwarder(mbs, null);
Forwarder ecd = new Forwarder(
JMXConnectorServer.EVENT_CLIENT_DELEGATE_FORWARDER, true,
ecdFwd.getClass());
Forwarder[] forwarders = {ecd};
Forwarder[] forwarders = {ctx, loc, ecd};
// Now go through every combination of forwarders. Each forwarder
// may be explicitly enabled, explicitly disabled, or left to its
@ -154,9 +165,11 @@ public class StandardForwardersTest {
}
MBeanServer stop = cs.getMBeanServer();
List<Class<?>> foundClasses = new ArrayList<Class<?>>();
for (MBeanServer mbs = cs.getSystemMBeanServer(); mbs != stop;
mbs = ((MBeanServerForwarder) mbs).getMBeanServer())
for (MBeanServer mbs = cs.getSystemMBeanServerForwarder().getMBeanServer();
mbs != stop;
mbs = ((MBeanServerForwarder) mbs).getMBeanServer()) {
foundClasses.add(mbs.getClass());
}
if (!expectedClasses.equals(foundClasses)) {
fail("Incorrect forwarder chain: expected " + expectedClasses +
"; found " + foundClasses);
@ -165,9 +178,12 @@ public class StandardForwardersTest {
// env is consistent if either (a) localizer is not enabled or (b)
// 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) {
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) {

View File

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

View File

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