changeset 699:810a95940b99

5072267: A way to communicate client context such as locale to the JMX server Summary: Support for client contexts and also for localization of descriptions Reviewed-by: dfuchs
author emcmanus
date Fri, 07 Nov 2008 11:48:07 +0100
parents 3a3e02a55de8
children 2410a0b48d06
files src/share/classes/com/sun/jmx/defaults/ServiceName.java src/share/classes/com/sun/jmx/event/EventParams.java src/share/classes/com/sun/jmx/event/LeaseManager.java src/share/classes/com/sun/jmx/interceptor/SingleMBeanForwarder.java src/share/classes/com/sun/jmx/mbeanserver/JmxMBeanServer.java src/share/classes/com/sun/jmx/namespace/JMXNamespaceUtils.java src/share/classes/com/sun/jmx/namespace/ObjectNameRouter.java src/share/classes/com/sun/jmx/namespace/RoutingConnectionProxy.java src/share/classes/com/sun/jmx/namespace/RoutingProxy.java src/share/classes/com/sun/jmx/namespace/RoutingServerProxy.java src/share/classes/com/sun/jmx/remote/util/EventClientConnection.java src/share/classes/javax/management/ClientContext.java src/share/classes/javax/management/Descriptor.java src/share/classes/javax/management/JMX.java src/share/classes/javax/management/MBeanInfo.java src/share/classes/javax/management/MBeanServerNotification.java src/share/classes/javax/management/Notification.java src/share/classes/javax/management/event/EventClient.java src/share/classes/javax/management/event/EventClientDelegate.java src/share/classes/javax/management/event/EventClientDelegateMBean.java src/share/classes/javax/management/event/EventRelay.java src/share/classes/javax/management/event/package-info.java src/share/classes/javax/management/namespace/JMXNamespaces.java src/share/classes/javax/management/namespace/JMXRemoteNamespace.java src/share/classes/javax/management/remote/JMXConnectorFactory.java src/share/classes/javax/management/remote/JMXConnectorServer.java src/share/classes/javax/management/remote/JMXConnectorServerMBean.java src/share/classes/javax/management/remote/rmi/RMIConnector.java src/share/classes/javax/management/remote/rmi/RMIConnectorServer.java test/javax/management/Introspector/AnnotationTest.java test/javax/management/context/ContextForwarderTest.java test/javax/management/context/ContextTest.java test/javax/management/context/LocaleAwareBroadcasterTest.java test/javax/management/context/LocaleTest.java test/javax/management/context/LocalizableTest.java test/javax/management/context/RemoteContextTest.java test/javax/management/context/localizable/MBeanDescriptions.properties test/javax/management/context/localizable/MBeanDescriptions_fr.java test/javax/management/context/localizable/Whatsit.java test/javax/management/context/localizable/WhatsitMBean.java test/javax/management/eventService/CustomForwarderTest.java test/javax/management/eventService/EventClientExecutorTest.java test/javax/management/eventService/EventManagerTest.java test/javax/management/eventService/ListenerTest.java test/javax/management/eventService/NotSerializableNotifTest.java test/javax/management/eventService/UsingEventService.java test/javax/management/namespace/EventWithNamespaceControlTest.java test/javax/management/namespace/JMXNamespaceSecurityTest.java test/javax/management/namespace/JMXNamespaceViewTest.java test/javax/management/namespace/JMXRemoteTargetNamespace.java test/javax/management/namespace/NamespaceNotificationsTest.java test/javax/management/namespace/NullDomainObjectNameTest.java test/javax/management/namespace/NullObjectNameTest.java test/javax/management/openmbean/CompositeDataStringTest.java test/javax/management/remote/mandatory/connectorServer/ForwarderChainTest.java test/javax/management/remote/mandatory/connectorServer/StandardForwardersTest.java test/javax/management/remote/mandatory/provider/ProviderTest.java test/javax/management/remote/mandatory/subjectDelegation/SimpleStandard.java
diffstat 58 files changed, 4875 insertions(+), 1547 deletions(-) [+]
line wrap: on
line diff
--- a/src/share/classes/com/sun/jmx/defaults/ServiceName.java	Thu Nov 06 12:12:39 2008 -0500
+++ b/src/share/classes/com/sun/jmx/defaults/ServiceName.java	Fri Nov 07 11:48:07 2008 +0100
@@ -69,9 +69,9 @@
     /**
      * 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.
--- a/src/share/classes/com/sun/jmx/event/EventParams.java	Thu Nov 06 12:12:39 2008 -0500
+++ b/src/share/classes/com/sun/jmx/event/EventParams.java	Fri Nov 07 11:48:07 2008 +0100
@@ -41,7 +41,7 @@
 
     @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);
--- a/src/share/classes/com/sun/jmx/event/LeaseManager.java	Thu Nov 06 12:12:39 2008 -0500
+++ b/src/share/classes/com/sun/jmx/event/LeaseManager.java	Fri Nov 07 11:48:07 2008 +0100
@@ -29,6 +29,7 @@
 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 @@
     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");
--- a/src/share/classes/com/sun/jmx/interceptor/SingleMBeanForwarder.java	Thu Nov 06 12:12:39 2008 -0500
+++ b/src/share/classes/com/sun/jmx/interceptor/SingleMBeanForwarder.java	Fri Nov 07 11:48:07 2008 +0100
@@ -55,9 +55,19 @@
 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 @@
             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 @@
 
     @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 @@
     @Override
     public Integer getMBeanCount() {
         Integer count = super.getMBeanCount();
-        if (!super.isRegistered(mbeanName))
+        if (visible && !super.isRegistered(mbeanName))
             count++;
         return count;
     }
@@ -284,7 +306,7 @@
      */
     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 @@
     @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 @@
     @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;
     }
--- a/src/share/classes/com/sun/jmx/mbeanserver/JmxMBeanServer.java	Thu Nov 06 12:12:39 2008 -0500
+++ b/src/share/classes/com/sun/jmx/mbeanserver/JmxMBeanServer.java	Fri Nov 07 11:48:07 2008 +0100
@@ -122,7 +122,7 @@
      *     {@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 @@
         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);
--- a/src/share/classes/com/sun/jmx/namespace/JMXNamespaceUtils.java	Thu Nov 06 12:12:39 2008 -0500
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -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;
-    }
-
-
-}
--- a/src/share/classes/com/sun/jmx/namespace/ObjectNameRouter.java	Thu Nov 06 12:12:39 2008 -0500
+++ b/src/share/classes/com/sun/jmx/namespace/ObjectNameRouter.java	Fri Nov 07 11:48:07 2008 +0100
@@ -49,11 +49,6 @@
     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 @@
             b.append(NAMESPACE_SEPARATOR);
         return b.toString();
     }
-
-
 }
--- a/src/share/classes/com/sun/jmx/namespace/RoutingConnectionProxy.java	Thu Nov 06 12:12:39 2008 -0500
+++ b/src/share/classes/com/sun/jmx/namespace/RoutingConnectionProxy.java	Fri Nov 07 11:48:07 2008 +0100
@@ -31,7 +31,6 @@
 import java.util.logging.Logger;
 
 import javax.management.MBeanServerConnection;
-import javax.management.namespace.JMXNamespaces;
 
 
 /**
@@ -61,18 +60,10 @@
      * 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 @@
         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 @@
         <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);
     }
 
 }
--- a/src/share/classes/com/sun/jmx/namespace/RoutingProxy.java	Thu Nov 06 12:12:39 2008 -0500
+++ b/src/share/classes/com/sun/jmx/namespace/RoutingProxy.java	Fri Nov 07 11:48:07 2008 +0100
@@ -30,6 +30,7 @@
 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 @@
 //     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 @@
     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 @@
             // 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 @@
             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 @@
     @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 @@
             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 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 @@
     // 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 @@
     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 @@
                 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);
     }
 }
--- a/src/share/classes/com/sun/jmx/namespace/RoutingServerProxy.java	Thu Nov 06 12:12:39 2008 -0500
+++ b/src/share/classes/com/sun/jmx/namespace/RoutingServerProxy.java	Fri Nov 07 11:48:07 2008 +0100
@@ -54,7 +54,6 @@
 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 @@
         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 @@
         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);
     }
 }
--- a/src/share/classes/com/sun/jmx/remote/util/EventClientConnection.java	Thu Nov 06 12:12:39 2008 -0500
+++ b/src/share/classes/com/sun/jmx/remote/util/EventClientConnection.java	Fri Nov 07 11:48:07 2008 +0100
@@ -430,13 +430,11 @@
      *        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");
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/share/classes/javax/management/ClientContext.java	Fri Nov 07 11:48:07 2008 +0100
@@ -0,0 +1,1091 @@
+/*
+ * 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.  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 javax.management;
+
+import com.sun.jmx.interceptor.SingleMBeanForwarder;
+import com.sun.jmx.namespace.RoutingConnectionProxy;
+import com.sun.jmx.namespace.RoutingProxy;
+import com.sun.jmx.namespace.RoutingServerProxy;
+import java.io.UnsupportedEncodingException;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.net.URLDecoder;
+import java.net.URLEncoder;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+import java.util.StringTokenizer;
+import java.util.TreeMap;
+import java.util.concurrent.Callable;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import static javax.management.namespace.JMXNamespaces.NAMESPACE_SEPARATOR;
+import javax.management.namespace.JMXNamespaces;
+import javax.management.namespace.JMXNamespace;
+import javax.management.namespace.JMXNamespaceMBean;
+import javax.management.namespace.MBeanServerSupport;
+import javax.management.remote.IdentityMBeanServerForwarder;
+import javax.management.remote.MBeanServerForwarder;
+
+/**
+ * <p>Methods to communicate a client context to MBeans.  A context is
+ * a {@literal Map<String, String>} that is provided by the client and
+ * that an MBean can consult using the {@link #getContext()} method.
+ * The context is set on a per-thread basis and can be consulted by any
+ * code that the target MBean calls within the thread.</p>
+ *
+ * <p>One common usage of client context is to communicate the client's
+ * {@link Locale} to MBeans.  For example, if an MBean has a String attribute
+ * {@code LastProblemDescription}, the value of that attribute could be
+ * a description of the last problem encountered by the MBean, translated
+ * into the client's locale.  Different clients accessing this attribute
+ * from different locales would each see the appropriate version for their
+ * locale.</p>
+ *
+ * <p>The locale case is sufficiently important that it has a special
+ * shorthand, the {@link #getLocale()} method.  This method calls
+ * <code>{@link #getContext()}.get({@link #LOCALE_KEY})</code> and converts the
+ * resultant String into a Locale object.</p>
+ *
+ * <p>Here is what an MBean with a localized {@code LastProblemDescription}
+ * attribute might look like:</p>
+ *
+ * <pre>
+ * public class LocaleSensitive implements LocaleSensitiveMBean {
+ *     ...
+ *     public String getLastProblemDescription() {
+ *         Locale loc = {@link #getLocale() ClientContext.getLocale()};
+ *         ResourceBundle rb = ResourceBundle.getBundle("MyResources", loc);
+ *         String resourceKey = getLastProblemResourceKey();
+ *         return rb.getString(resourceKey);
+ *     }
+ *     ...
+ * }
+ * </pre>
+ *
+ * <p>Here is how a client can communicate its locale to the target
+ * MBean:</p>
+ *
+ * <pre>
+ * JMXConnector connector = JMXConnectorFactory.connect(url);
+ * MBeanServerConnection connection = connector.getMBeanServerConnection();
+ * <b>MBeanServerConnection localizedConnection =
+ *     {@link #withLocale(MBeanServerConnection, Locale)
+ *      ClientContext.withLocale}(connection, Locale.getDefault());</b>
+ * String problem = localizedConnection.getAttribute(
+ *          objectName, "LastProblemDescription");
+ * </pre>
+ *
+ * <p>In the more general case where the client wants to communicate context
+ * other than the locale, it can use {@link #withContext(MBeanServerConnection,
+ * String, String) withContext} instead of {@code withLocale}, and the target
+ * MBean can retrieve the context using {@link #getContext()}.</p>
+ *
+ *
+ * <h3 id="remote-use">Remote use of contexts</h3>
+ *
+ * <p>The various {@code with*} methods, for example {@link
+ * #withLocale(javax.management.MBeanServer, java.util.Locale) withLocale},
+ * transmit the context of each request by encoding it in the ObjectName of
+ * the request.  For example, if a client creates a connection in the
+ * French locale like this...</p>
+ *
+ * <pre>
+ * MBeanServerConnection mbsc = ...;
+ * Locale french = new Locale("fr");
+ * MBeanServerConnection localizedConnection = ClientContext.withLocale(mbsc, french);
+ * </pre>
+ *
+ * <p>...or, equivalently, like this...</p>
+ *
+ * <pre>
+ * MBeanServerConnection localizedConnection =
+ *     ClientContext.withContext(mbsc, {@link #LOCALE_KEY "jmx.locale"}, "fr");
+ * </pre>
+ *
+ * <p>...then the context associates {@code "jmx.locale"} with {@code "fr"}
+ * and a request such as<br>
+ * {@code localizedConnection.getAttribute("java.lang:type=Runtime", "Name")}<br>
+ * is translated into<br>
+ * {@code mbsc.getAttribute("jmx.context//jmx.locale=fr//java.lang:Runtime", "Name")}.<br>
+ * A special {@linkplain javax.management.namespace namespace} {@code jmx.context//}
+ * extracts the context from the string {@code jmx.locale=fr} and establishes
+ * it in the thread that will do<br>
+ * {@code getAttribute("java.lang:Runtime", "Name")}.</p>
+ *
+ * <p>The details of how contexts are encoded into ObjectNames are explained
+ * in the {@link #encode encode} method.</p>
+ *
+ * <p>The namespace {@code jmx.context//} just mentioned is only needed by
+ * remote clients, since local clients can set the context directly using
+ * {@link #doWithContext doWithContext}.  Accordingly, this namespace is not
+ * present by default in the {@code MBeanServer}.  Instead, it is
+ * <em>simulated</em> by the standard RMI connector using a special
+ * {@link MBeanServerForwarder}.  If you are using this connector, you do not
+ * need to do anything special.  Other connectors may or may not simulate this
+ * namespace in the same way.  If the connector server returns true from the
+ * method {@link
+ * javax.management.remote.JMXConnectorServer#supportsSystemMBeanServerForwarder()
+ * supportsSystemMBeanServerForwarder} then it does simulate the namespace.
+ * If you are using another connector, or if you want to be able to use the
+ * {@code with*} methods locally, then you can install the {@code
+ * MBeanServerForwarder} yourself as described in the method {@link
+ * #newContextForwarder newContextForwarder}.</p>
+ */
+public class ClientContext {
+    /**
+     * <p>The context key for the client locale.  The string associated with
+     * this key is an encoded locale such as {@code en_US} which could be
+     * returned by {@link Locale#toString()}.</p>
+     */
+    public static final String LOCALE_KEY = "jmx.locale";
+
+    private static final Logger LOG =
+            Logger.getLogger("javax.management.context");
+
+    /**
+     * <p>The namespace that implements contexts, {@value}.</p>
+     */
+    public static final String
+            NAMESPACE = "jmx.context";
+    private static final String NAMESPACE_PLUS_SEP =
+            NAMESPACE + NAMESPACE_SEPARATOR;
+    static final ObjectName CLIENT_CONTEXT_NAMESPACE_HANDLER =
+            ObjectName.valueOf(NAMESPACE_PLUS_SEP + ":" +
+                    JMXNamespace.TYPE_ASSIGNMENT);
+    private static final ObjectName NAMESPACE_HANDLER_WITHOUT_NAMESPACE =
+            ObjectName.valueOf(":" + JMXNamespace.TYPE_ASSIGNMENT);
+
+    private static final ThreadLocal<Map<String, String>> contextThreadLocal =
+            new InheritableThreadLocal<Map<String, String>>() {
+        @Override
+        protected Map<String, String> initialValue() {
+            return Collections.emptyMap();
+        }
+    };
+
+    /** There are no instances of this class. */
+    private ClientContext() {
+    }
+
+    /**
+     * <p>Get the client context associated with the current thread.
+     *
+     * @return the client context associated with the current thread.
+     * This may be an empty Map, but it cannot be null.  The returned
+     * Map cannot be modified.
+     */
+    public static Map<String, String> getContext() {
+        return Collections.unmodifiableMap(contextThreadLocal.get());
+    }
+
+    /**
+     * <p>Get the client locale associated with the current thread.
+     * If the client context includes the {@value #LOCALE_KEY} key
+     * then the returned value is the Locale encoded in that key.
+     * Otherwise the returned value is the {@linkplain Locale#getDefault()
+     * default locale}.
+     *
+     * @return the client locale.
+     */
+    public static Locale getLocale() {
+        String localeS = getContext().get(LOCALE_KEY);
+        if (localeS == null)
+            return Locale.getDefault();
+        // Parse the locale string.  Why isn't there a method in Locale for this?
+        String language, country, variant;
+        int ui = localeS.indexOf('_');
+        if (ui < 0) {
+            language = localeS;
+            country = variant = "";
+        } else {
+            language = localeS.substring(0, ui);
+            localeS = localeS.substring(ui + 1);
+            ui = localeS.indexOf('_');
+            if (ui < 0) {
+                country = localeS;
+                variant = "";
+            } else {
+                country = localeS.substring(0, ui);
+                variant = localeS.substring(ui + 1);
+            }
+        }
+        return new Locale(language, country, variant);
+    }
+
+    /**
+     * <p>Execute the given {@code task} with the client context set to
+     * the given Map.  This Map will be the result of {@link #getContext()}
+     * within the {@code task}.</p>
+     *
+     * <p>The {@code task} may include nested calls to {@code doWithContext}.
+     * The value returned by {@link #getContext} at any point is the Map
+     * provided to the most recent {@code doWithContext} (in the current thread)
+     * that has not yet returned.</p>
+     *
+     * <p>The {@link #getContext()} method returns the same value immediately
+     * after a call to this method as immediately before.  In other words,
+     * {@code doWithContext} only affects the context during the execution of
+     * the {@code task}.</p>
+     *
+     * <p>As an example, suppose you want to get an attribute with whatever
+     * context has already been set, plus the locale set to "fr".  You could
+     * write this:</p>
+     *
+     * <pre>
+     * {@code Map<String, String>} context =
+     *     new {@code HashMap<String, String>}(ClientContext.getContext());
+     * context.put(ClientContext.LOCALE_KEY, "fr");
+     * String lastProblemDescription =
+     *     ClientContext.doWithContext(context, new {@code Callable<String>}() {
+     *         public String call() {
+     *             return (String) mbeanServer.getAttribute(mbean, "LastProblemDescription");
+     *         }
+     *     });
+     * </pre>
+     *
+     * @param <T> the type of value that the task will return.  This type
+     * parameter is usually inferred from the type of the {@code task}
+     * parameter.  For example, if {@code task} is a {@code Callable<String>}
+     * then {@code T} is {@code String}.  If the task does not return a value,
+     * use a {@code Callable<Void>} and return null from its
+     * {@link Callable#call call} method.
+     * @param context the context to use while executing {@code task}.
+     * @param task the task to run with the {@code key}={@code value}
+     * binding.
+     * @return the result of {@link Callable#call() task.call()}.
+     * @throws IllegalArgumentException if either parameter is null, or
+     * if any key in {@code context} is null or empty, or if any value
+     * in {@code context} is null.
+     * @throws Exception If {@link Callable#call() task.call()} throws an
+     * exception, {@code doWithContext} throws the same exception.
+     */
+    public static <T> T doWithContext(Map<String, String> context, Callable<T> task)
+    throws Exception {
+        if (context == null || task == null)
+            throw new IllegalArgumentException("Null parameter");
+        Map<String, String> contextCopy = new TreeMap<String, String>(context);
+        validateContext(contextCopy);
+        Map<String, String> oldContextMap = contextThreadLocal.get();
+        try {
+            contextThreadLocal.set(contextCopy);
+            return task.call();
+        } finally {
+            contextThreadLocal.set(oldContextMap);
+        }
+    }
+
+    private static void validateContext(Map<String, String> context) {
+        for (Map.Entry<String, String> entry : context.entrySet()) {
+            // If the user passes a raw Map rather than a Map<String, String>,
+            // entries could contain objects other than Strings.  If so,
+            // we'll get a ClassCastException here.
+            String key = entry.getKey();
+            String value = entry.getValue();
+            if (key == null || value == null)
+                throw new IllegalArgumentException("Null key or value in context");
+            if (key.equals(""))
+                throw new IllegalArgumentException("Empty key in context");
+        }
+    }
+
+    /**
+     * <p>Return an MBeanServer object that is equivalent to the given
+     * MBeanServer object except that operations on MBeans run with
+     * the given Locale in their {@linkplain #getContext() thread context}.
+     * Note that this will only work if the given MBeanServer supports
+     * contexts, as described <a href="#remote-use">above</a>.</p>
+     *
+     * <p>This method is equivalent to {@link #withContext(MBeanServer,
+     * String, String) withContext}<code>(mbs, {@value LOCALE_KEY},
+     * locale.toString())</code>.</p>
+     *
+     * @throws IllegalArgumentException if either parameter is null, or if
+     * {@code mbs} does not support contexts.  In the second case only,
+     * the cause of the {@code IllegalArgumentException} will be an {@link
+     * InstanceNotFoundException}.
+     */
+    public static MBeanServer withLocale(MBeanServer mbs, Locale locale) {
+        return withLocale(mbs, MBeanServer.class, locale);
+    }
+
+    /**
+     * <p>Return an MBeanServerConnection object that is equivalent to the given
+     * MBeanServerConnection object except that operations on MBeans run with
+     * the given Locale in their {@linkplain #getContext() thread context}.
+     * Note that this will only work if the given MBeanServerConnection supports
+     * contexts, as described <a href="#remote-use">above</a>.</p>
+     *
+     * <p>This method is equivalent to {@link #withContext(MBeanServerConnection,
+     * String, String) withContext}<code>(mbs, {@value LOCALE_KEY},
+     * locale.toString())</code>.</p>
+     *
+     * @throws IllegalArgumentException if either parameter is null, or if
+     * the communication with {@code mbsc} fails, or if {@code mbsc} does not
+     * support contexts.  If the communication with {@code mbsc} fails, the
+     * {@linkplain Throwable#getCause() cause} of this exception will be an
+     * {@code IOException}.  If {@code mbsc} does not support contexts, the
+     * cause will be an {@link InstanceNotFoundException}.
+     */
+    public static MBeanServerConnection withLocale(
+            MBeanServerConnection mbsc, Locale locale) {
+        return withLocale(mbsc, MBeanServerConnection.class, locale);
+    }
+
+    private static <T extends MBeanServerConnection> T withLocale(
+            T mbsc, Class<T> mbscClass, Locale locale) {
+        if (locale == null)
+            throw new IllegalArgumentException("Null locale");
+        return withContext(mbsc, mbscClass, LOCALE_KEY, locale.toString());
+    }
+
+    /**
+     * <p>Return an MBeanServer object that is equivalent to the given
+     * MBeanServer object except that operations on MBeans run with
+     * the given key bound to the given value in their {@linkplain
+     * #getContext() thread context}.
+     * Note that this will only work if the given MBeanServer supports
+     * contexts, as described <a href="#remote-use">above</a>.</p>
+     *
+     * @param mbs the original MBeanServer.
+     * @param key the key to bind in the context of MBean operations
+     * in the returned MBeanServer object.
+     * @param value the value to bind to the key in the context of MBean
+     * operations in the returned MBeanServer object.
+     * @throws IllegalArgumentException if any parameter is null, or
+     * if {@code key} is the empty string, or if {@code mbs} does not support
+     * contexts.  In the last case only, the cause of the {@code
+     * IllegalArgumentException} will be an {@link InstanceNotFoundException}.
+     */
+    public static MBeanServer withContext(
+            MBeanServer mbs, String key, String value) {
+        return withContext(mbs, MBeanServer.class, key, value);
+    }
+
+    /**
+     * <p>Return an MBeanServerConnection object that is equivalent to the given
+     * MBeanServerConnection object except that operations on MBeans run with
+     * the given key bound to the given value in their {@linkplain
+     * #getContext() thread context}.
+     * Note that this will only work if the given MBeanServerConnection supports
+     * contexts, as described <a href="#remote-use">above</a>.</p>
+     *
+     * @param mbsc the original MBeanServerConnection.
+     * @param key the key to bind in the context of MBean operations
+     * in the returned MBeanServerConnection object.
+     * @param value the value to bind to the key in the context of MBean
+     * operations in the returned MBeanServerConnection object.
+     * @throws IllegalArgumentException if any parameter is null, or
+     * if {@code key} is the empty string, or if the communication with {@code
+     * mbsc} fails, or if {@code mbsc} does not support contexts.  If
+     * the communication with {@code mbsc} fails, the {@linkplain
+     * Throwable#getCause() cause} of this exception will be an {@code
+     * IOException}.  If {@code mbsc} does not support contexts, the cause will
+     * be an {@link InstanceNotFoundException}.
+     */
+    public static MBeanServerConnection withContext(
+            MBeanServerConnection mbsc, String key, String value) {
+        return withContext(mbsc, MBeanServerConnection.class, key, value);
+    }
+
+
+    /**
+     * <p>Returns an MBeanServerConnection object that is equivalent to the
+     * given MBeanServerConnection object except that remote operations on
+     * MBeans run with the context that has been established by the client
+     * using {@link #doWithContext doWithContext}.  Note that this will
+     * only work if the remote system supports contexts, as described <a
+     * href="#remote-use">above</a>.</p>
+     *
+     * <p>For example, suppose the remote system does support contexts, and you
+     * have created a {@code JMXConnector} like this:</p>
+     *
+     * <pre>
+     * JMXServiceURL url = ...;
+     * JMXConnector client = JMXConnectorFactory.connect(url);
+     * MBeanServerConnection mbsc = client.getMBeanServerConnection();
+     * <b>mbsc = ClientContext.withDynamicContext(mbsc);</b>
+     * </pre>
+     *
+     * <p>Then if you do this...</p>
+     *
+     * <pre>
+     * MBeanInfo mbi = ClientContext.doWithContext(
+     *     Collections.singletonMap(ClientContext.LOCALE_KEY, "fr"),
+     *     new {@code Callable<MBeanInfo>}() {
+     *         public MBeanInfo call() {
+     *             return mbsc.getMBeanInfo(objectName);
+     *         }
+     *     });
+     * </pre>
+     *
+     * <p>...then the context with the locale set to "fr" will be in place
+     * when the {@code getMBeanInfo} is executed on the remote MBean Server.</p>
+     *
+     * @param mbsc the original MBeanServerConnection.
+     *
+     * @throws IllegalArgumentException if the {@code mbsc} parameter is null,
+     * or if the communication with {@code mbsc} fails, or if {@code mbsc}
+     * does not support contexts.  If the communication with {@code mbsc}
+     * fails, the {@linkplain Throwable#getCause() cause} of this exception
+     * will be an {@code IOException}.  If {@code mbsc} does not support
+     * contexts, the cause will be an {@link InstanceNotFoundException}.
+     */
+    public static MBeanServerConnection withDynamicContext(
+            MBeanServerConnection mbsc) {
+        // Probe mbsc to get the right exception if it's incommunicado or
+        // doesn't support namespaces.
+        JMXNamespaces.narrowToNamespace(mbsc, NAMESPACE);
+        return (MBeanServerConnection) Proxy.newProxyInstance(
+                MBeanServerConnection.class.getClassLoader(),
+                new Class<?>[] {MBeanServerConnection.class},
+                new DynamicContextIH(mbsc));
+    }
+
+    private static class DynamicContextIH implements InvocationHandler {
+        private final MBeanServerConnection mbsc;
+
+        public DynamicContextIH(MBeanServerConnection mbsc) {
+            this.mbsc = mbsc;
+        }
+
+        public Object invoke(Object proxy, Method method, Object[] args)
+                throws Throwable {
+            MBeanServerConnection dynMBSC = withContext(
+                    mbsc, MBeanServerConnection.class, getContext(), false);
+            try {
+                return method.invoke(dynMBSC, args);
+            } catch (InvocationTargetException e) {
+                throw e.getCause();
+            }
+        }
+    }
+
+    private static <T extends MBeanServerConnection> T withContext(
+            T mbsc, Class<T> mbscClass, String key, String value) {
+        return withContext(
+                mbsc, mbscClass, Collections.singletonMap(key, value), true);
+    }
+
+    private static <T extends MBeanServerConnection> T withContext(
+            T mbsc, Class<T> mbscClass, Map<String, String> context,
+            boolean probe) {
+        if (mbsc == null || context == null)
+            throw new IllegalArgumentException("Null parameter");
+        if (context.isEmpty())
+            return mbsc;
+        validateContext(context);
+        Map<String, String> contextMap = null;
+        if (mbsc.getClass() == RoutingServerProxy.class ||
+                mbsc.getClass() == RoutingProxy.class) {
+            RoutingProxy<?> nsp = (RoutingProxy<?>) mbsc;
+            String where = nsp.getSourceNamespace();
+            if (where.startsWith(NAMESPACE_PLUS_SEP)) {
+                /* Try to merge the existing context namespace with the
+                 * new one.  If it doesn't work, we fall back to just
+                 * prefixing jmx.context//key=value, which
+                 * might lead to a name like jmx.c//k1=v1//jmx.c//k2=v2//d:k=v.
+                 */
+                String encodedContext =
+                        where.substring(NAMESPACE_PLUS_SEP.length());
+                if (encodedContext.indexOf(NAMESPACE_SEPARATOR) < 0) {
+                    contextMap = stringToMapOrNull(encodedContext);
+                    if (contextMap != null) {
+                        contextMap.putAll(context);
+                        mbsc = mbscClass.cast(nsp.source());
+                    }
+                }
+            }
+        }
+        if (contextMap == null)
+            contextMap = context;
+        String contextDir = NAMESPACE_PLUS_SEP + mapToString(contextMap);
+        if (mbscClass == MBeanServer.class) {
+            return mbscClass.cast(RoutingServerProxy.cd(
+                    (MBeanServer) mbsc, contextDir, probe));
+        } else if (mbscClass == MBeanServerConnection.class) {
+            return mbscClass.cast(RoutingConnectionProxy.cd(
+                    mbsc, contextDir, probe));
+        } else
+            throw new AssertionError("Bad MBSC: " + mbscClass);
+    }
+
+    /**
+     * <p>Returns an encoded context prefix for ObjectNames.
+     * If the given context is empty, {@code ""} is returned.
+     * Otherwise, this method returns a string of the form
+     * {@code "jmx.context//key=value;key=value;..."}.
+     * For example, if the context has keys {@code "jmx.locale"}
+     * and {@code "xid"} with respective values {@code "fr"}
+     * and {@code "1234"}, this method will return
+     * {@code "jmx.context//jmx.locale=fr;xid=1234"} or
+     * {@code "jmx.context//xid=1234;jmx.locale=fr"}.</p>
+     *
+     * <p>Each key and each value in the encoded string is subject to
+     * encoding as if by the method {@link URLEncoder#encode(String, String)}
+     * with a character encoding of {@code "UTF-8"}, but with the additional
+     * encoding of any {@code *} character as {@code "%2A"}.  This ensures
+     * that keys and values can contain any character.  Without encoding,
+     * characters such as {@code =} and {@code :} would pose problems.</p>
+     *
+     * @param context the context to encode.
+     *
+     * @return the context in encoded form.
+     *
+     * @throws IllegalArgumentException if the {@code context} parameter
+     * is null or if it contains a null key or value.
+     **/
+    public static String encode(Map<String, String> context) {
+        if (context == null)
+            throw new IllegalArgumentException("Null context");
+        if (context.isEmpty())
+            return "";
+        StringBuilder sb = new StringBuilder();
+        for (Map.Entry<String, String> entry : context.entrySet()) {
+            String key = entry.getKey();
+            String value = entry.getValue();
+            if (key == null || value == null)
+                throw new IllegalArgumentException("Null key or value");
+            if (sb.length() > 0)
+                sb.append(";");
+            sb.append(encode(key)).append("=").append(encode(value));
+        }
+        sb.insert(0, NAMESPACE_PLUS_SEP);
+        return sb.toString();
+    }
+
+    /**
+     * <p>Create a new {@link MBeanServerForwarder} that applies the context
+     * received from a client to the current thread.  A client using
+     * one of the various {@code with*} methods (for example {@link
+     * #withContext(MBeanServerConnection, String, String) withContext}) will
+     * encode that context into the {@code ObjectName} of each
+     * {@code MBeanServer} request.  The object returned by this method
+     * decodes the context from that {@code ObjectName} and applies it
+     * as described for {@link #doWithContext doWithContext} while performing
+     * the {@code MBeanServer} request using the {@code ObjectName} without
+     * the encoded context.</p>
+     *
+     * <p>This forwarder can be used in a number of ways:</p>
+     *
+     * <ul>
+     * <li>
+     * <p>To add context decoding to a local {@code MBeanServer}, you can
+     * write:</p>
+     * <pre>
+     * MBeanServer mbs = {@link
+     * java.lang.management.ManagementFactory#getPlatformMBeanServer()
+     * ManagementFactory.getPlatformMBeanServer()};  // for example
+     * mbs = ClientContext.newContextForwarder(mbs, null);
+     * </pre>
+     *
+     * <li>
+     * <p>To add context decoding to a {@linkplain
+     * javax.management.remote.JMXConnectorServer connector server}:</p>
+     * <pre>
+     * JMXConnectorServer cs = JMXConnectorServerFactory.newJMXConnectorServer(...);
+     * MBeanServer nextMBS = cs.getMBeanServer();
+     * MBeanServerForwarder mbsf = ClientContext.newContextForwarder(nextMBS, null);
+     * cs.{@link
+     * javax.management.remote.JMXConnectorServer#setMBeanServerForwarder
+     * setMBeanServerForwarder}(mbsf);
+     * </pre>
+     *
+     * <li>
+     * <p>For connectors, such as the standard RMI connector, that support
+     * a {@linkplain
+     * javax.management.remote.JMXConnectorServer#getSystemMBeanServerForwarder
+     * system chain} of {@code MBeanServerForwarder}s, this forwarder will
+     * be installed in that chain by default.  See
+     * {@link javax.management.remote.JMXConnectorServer#CONTEXT_FORWARDER
+     * JMXConnectorServer.CONTEXT_FORWARDER}.
+     * </p>
+     *
+     * </ul>
+     *
+     * @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 include a context 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 that contain
+     * an encoded context should be sent once the context has been decoded.
+     * For example, if the request is {@link MBeanServer#getAttribute
+     * getAttribute}{@code ("jmx.context//jmx.locale=fr//java.lang:type=Runtime",
+     * "Name")}, then the {@linkplain #getContext() context} of the thread
+     * executing that request will have {@code "jmx.locale"} set to {@code "fr"}
+     * while executing {@code loopMBS.getAttribute("java.lang:type=Runtime",
+     * "Name")}.  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 decodes client context
+     * from {@code ObjectName}s.
+     */
+    /*
+     * What we're building here is confusing enough to need a diagram.
+     * The MBSF that we return is actually the composition of two forwarders:
+     * the first one simulates the existence of the MBean
+     * jmx.context//:type=JMXNamespace, and the second one simulates the
+     * existence of the namespace jmx.context//.  Furthermore, that namespace
+     * loops back to the composed forwarder, so that something like
+     * jmx.context//foo=bar//jmxcontext//baz=buh will work.  And the loopback
+     * goes through yet another forwarder, which simulates the existence of
+     * (e.g.) jmx.context//foo=bar//:type=JMXNamespace, which is needed
+     * notably so that narrowToNamespace will work.
+     *
+     *          |     +--------------------------------------------------+
+     *          v     v                                                  |
+     * +----------------+                                                |
+     * | Handler MBSF   |->accesses to jmx.context//:type=JMXNamespace   |
+     * +----------------+    (handled completely here)   +-------------------+
+     *          |                                        | 2nd Handler MBSF  |
+     *          v                                        +-------------------+
+     * +----------------+                                                ^
+     * | Namespace MBSF |->accesses to jmx.context//**-------------------+
+     * +----------------+    (after attaching context to thread)
+     *          |
+     *          v          accesses to anything else
+     *
+     * And finally, we need to ensure that from the outside the composed object
+     * looks like a single forwarder, so that its get/setMBeanServer methods
+     * will do the expected thing.  That's what the anonymous subclass is for.
+     */
+    public static MBeanServerForwarder newContextForwarder(
+            MBeanServer nextMBS, MBeanServer loopMBS) {
+        final MBeanServerForwarder mbsWrapper =
+                new IdentityMBeanServerForwarder(nextMBS);
+        DynamicMBean handlerMBean = new StandardMBean(
+                new JMXNamespace(mbsWrapper), JMXNamespaceMBean.class, false);
+        SingleMBeanForwarder handlerForwarder = new SingleMBeanForwarder(
+                CLIENT_CONTEXT_NAMESPACE_HANDLER, handlerMBean, true) {
+            @Override
+            public MBeanServer getMBeanServer() {
+                return ((MBeanServerForwarder) super.getMBeanServer()).getMBeanServer();
+            }
+
+            @Override
+            public void setMBeanServer(MBeanServer mbs1) {
+                MBeanServerForwarder mbsf1 = (MBeanServerForwarder)
+                        super.getMBeanServer();
+                if (mbsf1 != null)
+                    mbsf1.setMBeanServer(mbs1);
+                else
+                    super.setMBeanServer(mbs1);
+                mbsWrapper.setMBeanServer(mbs1);
+            }
+        };
+        if (loopMBS == null)
+            loopMBS = handlerForwarder;
+        ContextInvocationHandler contextIH =
+                new ContextInvocationHandler(nextMBS, loopMBS);
+        MBeanServerForwarder contextForwarder = newForwarderProxy(contextIH);
+        handlerForwarder.setMBeanServer(contextForwarder);
+        return handlerForwarder;
+    }
+
+    /**
+     * <p>Create a new {@link MBeanServerForwarder} that localizes
+     * descriptions in {@code MBeanInfo} instances returned by
+     * {@link MBeanServer#getMBeanInfo getMBeanInfo}.  The {@code
+     * MBeanServerForwarder} returned by this method passes all {@code
+     * MBeanServer} methods through unchanged to the supplied object, {@code
+     * mbs}, with the exception of {@code getMBeanInfo}.  To handle {@code
+     * getMBeanInfo(objectName)}, it calls {@code mbs.getMBeanInfo(objectName)}
+     * to get an {@code MBeanInfo}, {@code mbi}; it calls {@link
+     * MBeanServer#getClassLoaderFor mbs.getClassLoaderFor(objectName)} to
+     * get a {@code ClassLoader}, {@code cl}; and it calls {@link
+     * #getLocale} to get a {@code Locale}, {@code locale}.  The order
+     * of these three calls is not specified.  Then the result is {@code
+     * mbi.localizeDescriptions(locale, loader)}.</p>
+     *
+     * <p>This forwarder can be used in a number of ways:</p>
+     *
+     * <ul>
+     * <li>
+     * <p>To add description localization to a local {@code MBeanServer}, you
+     * can write:</p>
+     *
+     * <pre>
+     * MBeanServer mbs = {@link
+     * java.lang.management.ManagementFactory#getPlatformMBeanServer()
+     * ManagementFactory.getPlatformMBeanServer()};  // for example
+     * mbs = ClientContext.newLocalizeMBeanInfoForwarder(mbs);
+     * </pre>
+     *
+     * <li>
+     * <p>To add description localization to a {@linkplain
+     * javax.management.remote.JMXConnectorServer connector server}, you will
+     * need to add both a {@linkplain #newContextForwarder context forwarder}
+     * and a localization forwarder, for example like this:</p>
+     *
+     * <pre>
+     * JMXConnectorServer cs = JMXConnectorServerFactory.newJMXConnectorServer(...);
+     * MBeanServer nextMBS = cs.getMBeanServer();
+     * MBeanServerForwarder localizeMBSF =
+     *     ClientContext.newLocalizeMBeanInfoForwarder(nextMBS);
+     * MBeanServerForwarder contextMBSF =
+     *     ClientContext.newContextForwarder(localizeMBSF, null);
+     * cs.{@link
+     * javax.management.remote.JMXConnectorServer#setMBeanServerForwarder
+     * setMBeanServerForwarder}(contextMBSF);
+     * </pre>
+     *
+     * <p>Notice that the context forwarder must run before the localization
+     * forwarder, so that the locale is correctly established when the latter
+     * runs.  So the {@code nextMBS} parameter of the context forwarder must
+     * be the localization forwarder, and not vice versa.</p>
+     *
+     * <li>
+     * <p>For connectors, such as the standard RMI connector, that support
+     * a {@linkplain
+     * javax.management.remote.JMXConnectorServer#getSystemMBeanServerForwarder
+     * system chain} of {@code MBeanServerForwarder}s, the context forwarder and
+     * the localization forwarder will be installed in that chain, in the right
+     * order, if you include
+     * {@link
+     * javax.management.remote.JMXConnectorServer#LOCALIZE_MBEAN_INFO_FORWARDER
+     * LOCALIZE_MBEAN_INFO_FORWARDER} in the environment {@code Map} with
+     * the value {@code "true"}, for example like this:</p>
+     * </p>
+     * <pre>
+     * MBeanServer mbs = ...;
+     * JMXServiceURL url = new JMXServiceURL("service:jmx:rmi://...");
+     * {@code Map<String, Object>} env = new {@code HashMap<String, Object>}();
+     * env.put(JMXConnectorServer.LOCALIZE_MBEAN_INFO_FORWARDER, "true");
+     * JMXConnectorServer cs = JMXConnectorServerFactory.newJMXConnectorServer(
+     *     url, env, mbs);
+     * </pre>
+     *
+     * </ul>
+     *
+     * @param mbs the next {@code MBeanServer} in the chain of
+     * forwarders, which might be another {@code MBeanServerForwarder}
+     * or a plain {@code MBeanServer}.  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.
+     *
+     * @return a new {@code MBeanServerForwarder} that localizes descriptions
+     * in the result of {@code getMBeanInfo}.
+     */
+    public static MBeanServerForwarder newLocalizeMBeanInfoForwarder(
+            MBeanServer mbs) {
+        return new IdentityMBeanServerForwarder(mbs) {
+            @Override
+            public MBeanInfo getMBeanInfo(ObjectName name)
+                    throws InstanceNotFoundException, IntrospectionException,
+                           ReflectionException {
+                MBeanInfo mbi = super.getMBeanInfo(name);
+                Locale locale = getLocale();
+                ClassLoader loader = getClassLoaderFor(name);
+                return mbi.localizeDescriptions(locale, loader);
+            }
+        };
+    }
+
+    private static MBeanServerForwarder newForwarderProxy(InvocationHandler ih) {
+        return (MBeanServerForwarder) Proxy.newProxyInstance(
+                MBeanServerForwarder.class.getClassLoader(),
+                new Class<?>[] {MBeanServerForwarder.class},
+                ih);
+    }
+
+    // A proxy connection that will strip the 'contextDir' at input (routing),
+    // and put it back at output (createMBean / registerMBean / query* /
+    // getObjectInstance). Usually RoutingProxy / RoutingServerProxy are used
+    // the other way round (they are used for 'cd' - where they need to add
+    // something at input and remove it at output).
+    // For 'cd' operations we create RoutingProxys with a non empty sourceDir,
+    // and a possibly non-empty targetDir. This is the only case where we use
+    // RoutingProxies with an empty sourceDir (sourceDir is what we add at input
+    // and remove at output, targetDir is what we remove at input and add at
+    // output.
+    //
+    // Note that using a transient ContextRoutingConnection
+    // is possible only because RoutingProxys don't rewrite
+    // notifications sources - otherwise we would have to
+    // keep the ContextRoutingConnection - just to preserve
+    // the 'wrapping listeners'
+    //
+    private static final class ContextRoutingConnection
+            extends RoutingServerProxy {
+        public ContextRoutingConnection(MBeanServer source,
+                                 String contextDir) {
+            super(source, "", contextDir, false);
+        }
+
+        // Not really needed - but this is safer and more optimized.
+        // See RoutingProxy for more details.
+        //
+        @Override
+        public Integer getMBeanCount() {
+            return source().getMBeanCount();
+        }
+
+        // Not really needed - but this is safer and more optimized.
+        // See RoutingProxy for more details.
+        //
+        @Override
+        public String[] getDomains() {
+            return source().getDomains();
+        }
+
+        // Not really needed - but this is safer and more optimized.
+        // See RoutingProxy for more details.
+        //
+        @Override
+        public String getDefaultDomain() {
+            return source().getDefaultDomain();
+        }
+
+    }
+
+    private static class ContextInvocationHandler implements InvocationHandler {
+        /*
+         * MBeanServer requests that don't include jmx.context//foo=bar//
+         * are forwarded to forwardMBS, which is the unadorned MBeanServer
+         * that knows nothing about the context namespace.
+         * MBeanServer requests that do include this prefix will
+         * usually (depending on the value of the loopMBS parameter to
+         * newContextForwarder) loop back to the combined MBeanServerForwarder
+         * that first implements
+         * jmx.context//:type=JMXNamespace and then implements
+         * jmx.context//foo=bar//.  The reason is that it is valid
+         * to have jmx.context//foo=bar//jmx.context//baz=buh//, although
+         * usually that will be combined into jmx.context//foo=bar;baz=buh//.
+         *
+         * Before forwarding to loopMBS, we must check for :type=JMXNamespace
+         * so that jmx.context//foo=bar//:type=JMXNamespace will exist.  Its
+         * existence is partial because it must remain "invisible": it should
+         * not show up in queryNames or getMBeanCount even though it does
+         * accept getAttribute and isRegistered and all other methods that
+         * reference a single MBean.
+         */
+        private MBeanServer forwardMBS;
+        private final MBeanServer loopMBS;
+        private static final MBeanServer emptyMBS = new MBeanServerSupport() {
+            @Override
+            public DynamicMBean getDynamicMBeanFor(ObjectName name)
+                    throws InstanceNotFoundException {
+                throw new InstanceNotFoundException(name.toString());
+            }
+
+            @Override
+            protected Set<ObjectName> getNames() {
+                return Collections.emptySet();
+            }
+        };
+
+        ContextInvocationHandler(MBeanServer forwardMBS, MBeanServer loopMBS) {
+            this.forwardMBS = forwardMBS;
+            DynamicMBean handlerMBean = new StandardMBean(
+                    new JMXNamespace(loopMBS), JMXNamespaceMBean.class, false);
+            MBeanServerForwarder handlerMBS = new SingleMBeanForwarder(
+                    NAMESPACE_HANDLER_WITHOUT_NAMESPACE, handlerMBean, false);
+            handlerMBS.setMBeanServer(loopMBS);
+            this.loopMBS = handlerMBS;
+        }
+
+        public Object invoke(Object proxy, final Method method, final Object[] args)
+        throws Throwable {
+            String methodName = method.getName();
+            Class<?>[] paramTypes = method.getParameterTypes();
+
+            // If this is a method from MBeanServerForwarder, handle it here.
+            // There are only two such methods: getMBeanServer() and
+            // setMBeanServer(mbs).
+            if (methodName.equals("getMBeanServer"))
+                return forwardMBS;
+            else if (methodName.equals("setMBeanServer")) {
+                this.forwardMBS = (MBeanServer) args[0];
+                return null;
+            }
+
+            // It is a method from MBeanServer.
+            // Find the first parameter whose declared type is ObjectName,
+            // and see if it is in the context namespace.  If so we need to
+            // trigger the logic for that namespace.  If not, we simply
+            // forward to the next MBeanServer in the chain.  This logic
+            // depends on the fact that if a method in the MBeanServer interface
+            // has a "routing" ObjectName parameter, it is always the first
+            // parameter of that type.  Conversely, if a method has an
+            // ObjectName parameter, then it makes sense to "route" that
+            // method.  Except for deserialize and instantiate, but if we
+            // recognize a context namespace in those methods' ObjectName
+            // parameters it is pretty harmless.
+            int objectNameI = -1;
+            for (int i = 0; i < paramTypes.length; i++) {
+                if (paramTypes[i] == ObjectName.class) {
+                    objectNameI = i;
+                    break;
+                }
+            }
+
+            if (objectNameI < 0)
+                return invoke(method, forwardMBS, args);
+
+            ObjectName target = (ObjectName) args[objectNameI];
+            if (target == null ||
+                    !target.getDomain().startsWith(NAMESPACE_PLUS_SEP))
+                return invoke(method, forwardMBS, args);
+
+            String domain = target.getDomain().substring(NAMESPACE_PLUS_SEP.length());
+
+            // The method routes through the (simulated) context namespace.
+            // Decode the context after it, e.g. jmx.context//jmx.locale=fr//...
+            // If there is no context part, we can throw an exception,
+            // because a forwarder has already handled the unique MBean
+            // jmx.context//:type=JMXNamespace.
+            int sep = domain.indexOf(NAMESPACE_SEPARATOR);
+            if (sep < 0)
+                return invoke(method, emptyMBS, args);  // throw exception
+            final String encodedContext = domain.substring(0, sep);
+
+            if (method.getName().startsWith("query") &&
+                    (encodedContext.contains("*") || encodedContext.contains("?"))) {
+                // Queries like jmx.context//*//d:k=v return
+                // an empty set, consistent with "real" namespaces.
+                return Collections.EMPTY_SET;
+            }
+
+            Map<String, String> ctx = new TreeMap<String, String>(getContext());
+            ctx.putAll(stringToMap(encodedContext));
+
+            return doWithContext(ctx, new Callable<Object>() {
+                public Object call() throws Exception {
+                    // Create a proxy connection that will strip
+                    // "jmx.context//" + encodedContext + "//" on input,
+                    // and put it back on output.
+                    //
+                    // Note that using a transient ContextRoutingConnection
+                    // is possible only because it doesn't rewrite
+                    // notification sources - otherwise we would have to
+                    // keep the ContextRoutingConnection - just to preserve
+                    // the 'wrapping listeners'
+                    //
+                    String namespace = NAMESPACE_PLUS_SEP + encodedContext;
+                    final ContextRoutingConnection route =
+                              new ContextRoutingConnection(loopMBS, namespace);
+
+                    if (LOG.isLoggable(Level.FINE))
+                        LOG.fine("context="+encodedContext);
+                    if (LOG.isLoggable(Level.FINER))
+                        LOG.finer(method.getName()+""+
+                            ((args==null)?"()":(""+Arrays.asList(args))));
+
+                    return invoke(method, route, args);
+                }
+            });
+        }
+
+        private static Object invoke(Method method, Object target, Object[] args)
+                throws Exception {
+            try {
+                return method.invoke(target, args);
+            } catch (InvocationTargetException e) {
+                Throwable cause = e.getCause();
+                if (cause instanceof Error)
+                    throw (Error) cause;
+                throw (Exception) cause;
+            }
+        }
+    }
+
+    private static String mapToString(Map<String, String> map) {
+        StringBuilder sb = new StringBuilder();
+        for (Map.Entry<String, String> entry : map.entrySet()) {
+            String key = encode(entry.getKey());
+            String value = encode(entry.getValue());
+            if (sb.length() > 0)
+                sb.append(";");
+            sb.append(key).append("=").append(value);
+        }
+        return sb.toString();
+    }
+
+    private static Map<String, String> stringToMap(String encodedContext) {
+        Map<String, String> map = stringToMapOrNull(encodedContext);
+        if (map == null) {
+            throw new IllegalArgumentException(
+                    "Invalid encoded context: " + encodedContext);
+        }
+        return map;
+    }
+
+    private static Map<String, String> stringToMapOrNull(String encodedContext) {
+        Map<String, String> map = new LinkedHashMap<String, String>();
+        StringTokenizer stok = new StringTokenizer(encodedContext, ";");
+        while (stok.hasMoreTokens()) {
+            String tok = stok.nextToken();
+            int eq = tok.indexOf('=');
+            if (eq < 0)
+                return null;
+            String key = decode(tok.substring(0, eq));
+            if (key.equals(""))
+                return null;
+            String value = decode(tok.substring(eq + 1));
+            map.put(key, value);
+        }
+        return map;
+    }
+
+    private static String encode(String s) {
+        try {
+            s = URLEncoder.encode(s, "UTF-8");
+        } catch (UnsupportedEncodingException e) {
+            throw new RuntimeException(e);  // Should not happen
+        }
+        return s.replace("*", "%2A");
+        // The * character is left intact in URL encodings, but for us it
+        // is special (an ObjectName wildcard) so we must map it.
+        // We are assuming that URLDecoder will decode it the same way as any
+        // other hex escape.
+    }
+
+    private static String decode(String s) {
+        try {
+            return URLDecoder.decode(s, "UTF-8");
+        } catch (UnsupportedEncodingException e) {
+            throw new RuntimeException(e);
+        }
+    }
+}
--- a/src/share/classes/javax/management/Descriptor.java	Thu Nov 06 12:12:39 2008 -0500
+++ b/src/share/classes/javax/management/Descriptor.java	Fri Nov 07 11:48:07 2008 +0100
@@ -35,8 +35,8 @@
 // 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 @@
  * 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 @@
  * 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 @@
  * <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>
  *
--- a/src/share/classes/javax/management/JMX.java	Thu Nov 06 12:12:39 2008 -0500
+++ b/src/share/classes/javax/management/JMX.java	Fri Nov 07 11:48:07 2008 +0100
@@ -30,6 +30,7 @@
 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.Proxy;
 import java.util.Map;
 import java.util.TreeMap;
+import javax.management.namespace.JMXNamespaces;
 import javax.management.openmbean.MXBeanMappingFactory;
 
 /**
@@ -60,6 +62,21 @@
      */
     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.
@@ -79,6 +96,12 @@
     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 @@
      * <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>
      *
--- a/src/share/classes/javax/management/MBeanInfo.java	Thu Nov 06 12:12:39 2008 -0500
+++ b/src/share/classes/javax/management/MBeanInfo.java	Fri Nov 07 11:48:07 2008 +0100
@@ -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.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 @@
      * <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 @@
         return (Descriptor) nonNullDescriptor(descriptor).clone();
     }
 
+    @Override
     public String toString() {
         return
             getClass().getName() + "[" +
@@ -505,6 +514,7 @@
      * @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 @@
              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 @@
             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");
+        }
+    }
 }
--- a/src/share/classes/javax/management/MBeanServerNotification.java	Thu Nov 06 12:12:39 2008 -0500
+++ b/src/share/classes/javax/management/MBeanServerNotification.java	Fri Nov 07 11:48:07 2008 +0100
@@ -27,15 +27,43 @@
 
 
 /**
- * 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 {
--- a/src/share/classes/javax/management/Notification.java	Thu Nov 06 12:12:39 2008 -0500
+++ b/src/share/classes/javax/management/Notification.java	Fri Nov 07 11:48:07 2008 +0100
@@ -54,7 +54,7 @@
  * @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
@@ -244,6 +244,26 @@
     }
 
     /**
+     * <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.
      *
      * @param source the new source for this object.
@@ -285,8 +305,10 @@
     /**
      * 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,15 +339,26 @@
     /**
      * 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.
      *
      * @return The user data object. It is used for whatever data
@@ -355,6 +388,7 @@
      *
      * @return A String representation of this notification.
      */
+    @Override
     public String toString() {
         return super.toString()+"[type="+type+"][message="+message+"]";
     }
--- a/src/share/classes/javax/management/event/EventClient.java	Thu Nov 06 12:12:39 2008 -0500
+++ b/src/share/classes/javax/management/event/EventClient.java	Fri Nov 07 11:48:07 2008 +0100
@@ -29,7 +29,6 @@
 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.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 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 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 @@
      * 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 @@
         }
 
         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 @@
                         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 @@
                 // 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 @@
             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 @@
         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();
 }
--- a/src/share/classes/javax/management/event/EventClientDelegate.java	Thu Nov 06 12:12:39 2008 -0500
+++ b/src/share/classes/javax/management/event/EventClientDelegate.java	Fri Nov 07 11:48:07 2008 +0100
@@ -149,6 +149,7 @@
     // 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 @@
         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 @@
                         AccessController.doPrivileged(
                             new PrivilegedAction<EventClientDelegate>() {
                                 public EventClientDelegate run() {
-                                    return getEventClientDelegate(Forwarder.this);
+                                    return getEventClientDelegate(loopMBS);
                                 }
                             });
                     DynamicMBean mbean = new StandardMBean(
@@ -208,11 +213,46 @@
      * 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 @@
     // 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 @@
         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;
--- a/src/share/classes/javax/management/event/EventClientDelegateMBean.java	Thu Nov 06 12:12:39 2008 -0500
+++ b/src/share/classes/javax/management/event/EventClientDelegateMBean.java	Fri Nov 07 11:48:07 2008 +0100
@@ -51,7 +51,8 @@
  * 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 @@
  *
  * <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>
  *
--- a/src/share/classes/javax/management/event/EventRelay.java	Thu Nov 06 12:12:39 2008 -0500
+++ b/src/share/classes/javax/management/event/EventRelay.java	Fri Nov 07 11:48:07 2008 +0100
@@ -27,7 +27,6 @@
 
 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
--- a/src/share/classes/javax/management/event/package-info.java	Thu Nov 06 12:12:39 2008 -0500
+++ b/src/share/classes/javax/management/event/package-info.java	Fri Nov 07 11:48:07 2008 +0100
@@ -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>
  *
--- a/src/share/classes/javax/management/namespace/JMXNamespaces.java	Thu Nov 06 12:12:39 2008 -0500
+++ b/src/share/classes/javax/management/namespace/JMXNamespaces.java	Fri Nov 07 11:48:07 2008 +0100
@@ -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
@@ -69,23 +67,6 @@
 
 
     /**
-     * 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}
      * of the given parent.
@@ -96,15 +77,18 @@
      *               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 @@
      *         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 @@
                 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+
--- a/src/share/classes/javax/management/namespace/JMXRemoteNamespace.java	Thu Nov 06 12:12:39 2008 -0500
+++ b/src/share/classes/javax/management/namespace/JMXRemoteNamespace.java	Fri Nov 07 11:48:07 2008 +0100
@@ -27,10 +27,10 @@
 
 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 javax.management.AttributeChangeNotification;
 
+import javax.management.ClientContext;
 import javax.management.InstanceNotFoundException;
 import javax.management.ListenerNotFoundException;
 import javax.management.MBeanNotificationInfo;
@@ -220,17 +221,26 @@
                 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 @@
         }
     }
 
-    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 @@
         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 @@
         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);
--- a/src/share/classes/javax/management/remote/JMXConnectorFactory.java	Thu Nov 06 12:12:39 2008 -0500
+++ b/src/share/classes/javax/management/remote/JMXConnectorFactory.java	Fri Nov 07 11:48:07 2008 +0100
@@ -322,10 +322,12 @@
                 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 @@
             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 @@
                     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 @@
                 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 @@
     private static String protocol2package(String protocol) {
         return protocol.replace('+', '.').replace('-', '_');
     }
+
 }
--- a/src/share/classes/javax/management/remote/JMXConnectorServer.java	Thu Nov 06 12:12:39 2008 -0500
+++ b/src/share/classes/javax/management/remote/JMXConnectorServer.java	Fri Nov 07 11:48:07 2008 +0100
@@ -33,6 +33,7 @@
 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;
@@ -103,6 +104,56 @@
 
      /**
       * <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}
       * MBean. The value associated with this attribute, if any, must
       * be a string that is equal to {@code "true"} or {@code "false"},
@@ -155,7 +206,7 @@
      * 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 @@
      * 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 @@
      * 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 @@
      * 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 @@
      *
      * @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 @@
      * <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 @@
      *
      * @return the array of possible notifications.
      */
+    @Override
     public MBeanNotificationInfo[] getNotificationInfo() {
         final String[] types = {
             JMXConnectionNotification.OPENED,
@@ -684,30 +764,29 @@
      * 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 @@
      * 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 @@
 
     private MBeanServer userMBeanServer;
 
-    private MBeanServer systemMBeanServer;
+    private final MBeanServerForwarder systemMBeanServerForwarder =
+            new IdentityMBeanServerForwarder();
 
     /**
      * The name used to registered this server in an MBeanServer.
--- a/src/share/classes/javax/management/remote/JMXConnectorServerMBean.java	Thu Nov 06 12:12:39 2008 -0500
+++ b/src/share/classes/javax/management/remote/JMXConnectorServerMBean.java	Fri Nov 07 11:48:07 2008 +0100
@@ -132,7 +132,7 @@
      *
      * <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 @@
      * 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);
 
--- a/src/share/classes/javax/management/remote/rmi/RMIConnector.java	Thu Nov 06 12:12:39 2008 -0500
+++ b/src/share/classes/javax/management/remote/rmi/RMIConnector.java	Fri Nov 07 11:48:07 2008 +0100
@@ -134,14 +134,14 @@
 public class RMIConnector implements JMXConnector, Serializable, JMXAddressable {
 
     private static final ClassLogger logger =
-        new ClassLogger("javax.management.remote.rmi", "RMIConnector");
+            new ClassLogger("javax.management.remote.rmi", "RMIConnector");
 
     private static final long serialVersionUID = 817323035842634473L;
 
     private RMIConnector(RMIServer rmiServer, JMXServiceURL address,
-                         Map<String, ?> environment) {
+            Map<String, ?> environment) {
         if (rmiServer == null && address == null) throw new
-          IllegalArgumentException("rmiServer and jmxServiceURL both null");
+                IllegalArgumentException("rmiServer and jmxServiceURL both null");
 
         initTransients();
 
@@ -257,7 +257,7 @@
     }
 
     public synchronized void connect(Map<String,?> environment)
-            throws IOException {
+    throws IOException {
         final boolean tracing = logger.traceOn();
         String        idstr   = (tracing?"["+this.toString()+"]":null);
 
@@ -274,8 +274,8 @@
             if (tracing) logger.trace("connect",idstr + " connecting...");
 
             final Map<String, Object> usemap =
-                new HashMap<String, Object>((this.env==null) ?
-                    Collections.<String, Object>emptyMap() : this.env);
+                    new HashMap<String, Object>((this.env==null) ?
+                        Collections.<String, Object>emptyMap() : this.env);
 
 
             if (environment != null) {
@@ -315,7 +315,7 @@
             defaultClassLoader = EnvHelp.resolveClientClassLoader(usemap);
 
             usemap.put(JMXConnectorFactory.DEFAULT_CLASS_LOADER,
-                       defaultClassLoader);
+                    defaultClassLoader);
 
             rmiNotifClient = new RMINotifClient(defaultClassLoader, usemap);
 
@@ -333,12 +333,12 @@
             eventServiceEnabled = EnvHelp.eventServiceEnabled(env);
 
             Notification connectedNotif =
-                new JMXConnectionNotification(JMXConnectionNotification.OPENED,
-                                              this,
-                                              connectionId,
-                                              clientNotifSeqNo++,
-                                              "Successful connection",
-                                              null);
+                    new JMXConnectionNotification(JMXConnectionNotification.OPENED,
+                    this,
+                    connectionId,
+                    clientNotifSeqNo++,
+                    "Successful connection",
+                    null);
             sendNotification(connectedNotif);
 
             // whether or not event service
@@ -363,7 +363,7 @@
         if (terminated || !connected) {
             if (logger.traceOn())
                 logger.trace("getConnectionId","["+this.toString()+
-                      "] not connected.");
+                        "] not connected.");
 
             throw new IOException("Not connected");
         }
@@ -374,23 +374,23 @@
     }
 
     public synchronized MBeanServerConnection getMBeanServerConnection()
-            throws IOException {
+    throws IOException {
         return getMBeanServerConnection(null);
     }
 
     public synchronized MBeanServerConnection
-        getMBeanServerConnection(Subject delegationSubject)
+            getMBeanServerConnection(Subject delegationSubject)
             throws IOException {
 
         if (terminated) {
             if (logger.traceOn())
                 logger.trace("getMBeanServerConnection","[" + this.toString() +
-                      "] already closed.");
+                        "] already closed.");
             throw new IOException("Connection closed");
         } else if (!connected) {
             if (logger.traceOn())
                 logger.trace("getMBeanServerConnection","[" + this.toString() +
-                      "] is not connected.");
+                        "] is not connected.");
             throw new IOException("Not connected");
         }
 
@@ -405,7 +405,7 @@
                     rmbsc, EventClientDelegateMBean.OBJECT_NAME,
                     EventClientDelegateMBean.class);
             EventClient ec = new EventClient(ecd, null, defaultExecutor(), null,
-                    EventClient.DEFAULT_LEASE_TIMEOUT);
+                    EventClient.DEFAULT_REQUESTED_LEASE_TIME);
 
             rmbsc = EventConnection.Factory.make(rmbsc, ec);
             ec.addEventClientListener(
@@ -433,17 +433,17 @@
     }
 
     public void
-        addConnectionNotificationListener(NotificationListener listener,
-                                          NotificationFilter filter,
-                                          Object handback) {
+            addConnectionNotificationListener(NotificationListener listener,
+            NotificationFilter filter,
+            Object handback) {
         if (listener == null)
             throw new NullPointerException("listener");
         connectionBroadcaster.addNotificationListener(listener, filter,
-                                                      handback);
+                handback);
     }
 
     public void
-        removeConnectionNotificationListener(NotificationListener listener)
+            removeConnectionNotificationListener(NotificationListener listener)
             throws ListenerNotFoundException {
         if (listener == null)
             throw new NullPointerException("listener");
@@ -451,14 +451,14 @@
     }
 
     public void
-        removeConnectionNotificationListener(NotificationListener listener,
-                                             NotificationFilter filter,
-                                             Object handback)
+            removeConnectionNotificationListener(NotificationListener listener,
+            NotificationFilter filter,
+            Object handback)
             throws ListenerNotFoundException {
         if (listener == null)
             throw new NullPointerException("listener");
         connectionBroadcaster.removeNotificationListener(listener, filter,
-                                                         handback);
+                handback);
     }
 
     private void sendNotification(Notification n) {
@@ -526,11 +526,11 @@
             try {
                 rmiNotifClient.terminate();
                 if (tracing) logger.trace("close",idstr +
-                                    " RMI Notification client terminated.");
+                        " RMI Notification client terminated.");
             } catch (RuntimeException x) {
                 closeException = x;
                 if (tracing) logger.trace("close",idstr +
-                         " Failed to terminate RMI Notification client: " + x);
+                        " Failed to terminate RMI Notification client: " + x);
                 if (debug) logger.debug("close",x);
             }
         }
@@ -544,7 +544,7 @@
             } catch (IOException e) {
                 closeException = e;
                 if (tracing) logger.trace("close",idstr +
-                                          " Failed to close RMIServer: " + e);
+                        " Failed to close RMIServer: " + e);
                 if (debug) logger.debug("close",e);
             }
         }
@@ -559,12 +559,12 @@
 
         if (savedConnectionId != null) {
             Notification closedNotif =
-                new JMXConnectionNotification(JMXConnectionNotification.CLOSED,
-                                              this,
-                                              savedConnectionId,
-                                              clientNotifSeqNo++,
-                                              "Client has been closed",
-                                              null);
+                    new JMXConnectionNotification(JMXConnectionNotification.CLOSED,
+                    this,
+                    savedConnectionId,
+                    clientNotifSeqNo++,
+                    "Client has been closed",
+                    null);
             sendNotification(closedNotif);
         }
 
@@ -572,13 +572,13 @@
         //
         if (closeException != null) {
             if (tracing) logger.trace("close",idstr + " failed to close: " +
-                               closeException);
+                    closeException);
             if (closeException instanceof IOException)
                 throw (IOException) closeException;
             if (closeException instanceof RuntimeException)
                 throw (RuntimeException) closeException;
             final IOException x =
-                new IOException("Failed to close: " + closeException);
+                    new IOException("Failed to close: " + closeException);
             throw EnvHelp.initCause(x,closeException);
         }
     }
@@ -593,7 +593,7 @@
         final boolean debug = logger.debugOn();
         if (debug)
             logger.debug("addListenerWithSubject",
-                         "(ObjectName,MarshalledObject,Subject)");
+                    "(ObjectName,MarshalledObject,Subject)");
 
         final ObjectName[] names = new ObjectName[] {name};
         final MarshalledObject<NotificationFilter>[] filters =
@@ -603,11 +603,11 @@
         };
 
         final Integer[] listenerIDs =
-            addListenersWithSubjects(names,filters,delegationSubjects,
-                                          reconnect);
+                addListenersWithSubjects(names,filters,delegationSubjects,
+                reconnect);
 
         if (debug) logger.debug("addListenerWithSubject","listenerID="
-                                + listenerIDs[0]);
+                + listenerIDs[0]);
         return listenerIDs[0];
     }
 
@@ -620,24 +620,24 @@
 
         final boolean debug = logger.debugOn();
         if (debug)
-        logger.debug("addListenersWithSubjects",
-                         "(ObjectName[],MarshalledObject[],Subject[])");
+            logger.debug("addListenersWithSubjects",
+                    "(ObjectName[],MarshalledObject[],Subject[])");
 
         final ClassLoader old = pushDefaultClassLoader();
         Integer[] listenerIDs = null;
 
         try {
             listenerIDs = connection.addNotificationListeners(names,
-                                                              filters,
-                                                          delegationSubjects);
+                    filters,
+                    delegationSubjects);
         } catch (NoSuchObjectException noe) {
             // maybe reconnect
             if (reconnect) {
                 communicatorAdmin.gotIOException(noe);
 
                 listenerIDs = connection.addNotificationListeners(names,
-                                                       filters,
-                                                       delegationSubjects);
+                        filters,
+                        delegationSubjects);
             } else {
                 throw noe;
             }
@@ -671,65 +671,65 @@
         }
 
         public ObjectInstance createMBean(String className,
-                                          ObjectName name)
+                ObjectName name)
                 throws ReflectionException,
-                       InstanceAlreadyExistsException,
-                       MBeanRegistrationException,
-                       MBeanException,
-                       NotCompliantMBeanException,
-                       IOException {
+                InstanceAlreadyExistsException,
+                MBeanRegistrationException,
+                MBeanException,
+                NotCompliantMBeanException,
+                IOException {
             if (logger.debugOn())
                 logger.debug("createMBean(String,ObjectName)",
-                             "className=" + className + ", name=" +
-                             name);
+                        "className=" + className + ", name=" +
+                        name);
 
             final ClassLoader old = pushDefaultClassLoader();
             try {
                 return connection.createMBean(className,
-                                             name,
-                                             delegationSubject);
+                        name,
+                        delegationSubject);
             } catch (IOException ioe) {
                 communicatorAdmin.gotIOException(ioe);
 
                 return connection.createMBean(className,
-                                             name,
-                                             delegationSubject);
+                        name,
+                        delegationSubject);
             } finally {
                 popDefaultClassLoader(old);
             }
         }
 
         public ObjectInstance createMBean(String className,
-                                          ObjectName name,
-                                          ObjectName loaderName)
-            throws ReflectionException,
-                       InstanceAlreadyExistsException,
-                       MBeanRegistrationException,
-                       MBeanException,
-                       NotCompliantMBeanException,
-                       InstanceNotFoundException,
-                       IOException {
+                ObjectName name,
+                ObjectName loaderName)
+                throws ReflectionException,
+                InstanceAlreadyExistsException,
+                MBeanRegistrationException,
+                MBeanException,
+                NotCompliantMBeanException,
+                InstanceNotFoundException,
+                IOException {
 
             if (logger.debugOn())
                 logger.debug("createMBean(String,ObjectName,ObjectName)",
-                      "className=" + className + ", name="
-                      + name + ", loaderName="
-                      + loaderName + ")");
+                        "className=" + className + ", name="
+                        + name + ", loaderName="
+                        + loaderName + ")");
 
             final ClassLoader old = pushDefaultClassLoader();
             try {
                 return connection.createMBean(className,
-                                             name,
-                                             loaderName,
-                                             delegationSubject);
+                        name,
+                        loaderName,
+                        delegationSubject);
 
             } catch (IOException ioe) {
                 communicatorAdmin.gotIOException(ioe);
 
                 return connection.createMBean(className,
-                                             name,
-                                             loaderName,
-                                             delegationSubject);
+                        name,
+                        loaderName,
+                        delegationSubject);
 
             } finally {
                 popDefaultClassLoader(old);
@@ -737,90 +737,90 @@
         }
 
         public ObjectInstance createMBean(String className,
-                                          ObjectName name,
-                                          Object params[],
-                                          String signature[])
+                ObjectName name,
+                Object params[],
+                String signature[])
                 throws ReflectionException,
-                       InstanceAlreadyExistsException,
-                       MBeanRegistrationException,
-                       MBeanException,
-                       NotCompliantMBeanException,
-                       IOException {
+                InstanceAlreadyExistsException,
+                MBeanRegistrationException,
+                MBeanException,
+                NotCompliantMBeanException,
+                IOException {
             if (logger.debugOn())
-               logger.debug("createMBean(String,ObjectName,Object[],String[])",
-                      "className=" + className + ", name="
-                      + name + ", params="
-                      + objects(params) + ", signature="
-                      + strings(signature));
+                logger.debug("createMBean(String,ObjectName,Object[],String[])",
+                        "className=" + className + ", name="
+                        + name + ", params="
+                        + objects(params) + ", signature="
+                        + strings(signature));
 
             final MarshalledObject<Object[]> sParams =
                     new MarshalledObject<Object[]>(params);
             final ClassLoader old = pushDefaultClassLoader();
             try {
                 return connection.createMBean(className,
-                                             name,
-                                             sParams,
-                                             signature,
-                                             delegationSubject);
+                        name,
+                        sParams,
+                        signature,
+                        delegationSubject);
             } catch (IOException ioe) {
                 communicatorAdmin.gotIOException(ioe);
 
                 return connection.createMBean(className,
-                                             name,
-                                             sParams,
-                                             signature,
-                                             delegationSubject);
+                        name,
+                        sParams,
+                        signature,
+                        delegationSubject);
             } finally {
                 popDefaultClassLoader(old);
             }
         }
 
         public ObjectInstance createMBean(String className,
-                                          ObjectName name,
-                                          ObjectName loaderName,
-                                          Object params[],
-                                          String signature[])
+                ObjectName name,
+                ObjectName loaderName,
+                Object params[],
+                String signature[])
                 throws ReflectionException,
-                       InstanceAlreadyExistsException,
-                       MBeanRegistrationException,
-                       MBeanException,
-                       NotCompliantMBeanException,
-                       InstanceNotFoundException,
-                       IOException {
+                InstanceAlreadyExistsException,
+                MBeanRegistrationException,
+                MBeanException,
+                NotCompliantMBeanException,
+                InstanceNotFoundException,
+                IOException {
             if (logger.debugOn()) logger.debug(
-                "createMBean(String,ObjectName,ObjectName,Object[],String[])",
-                "className=" + className + ", name=" + name + ", loaderName="
-                + loaderName + ", params=" + objects(params)
-                + ", signature=" + strings(signature));
+                    "createMBean(String,ObjectName,ObjectName,Object[],String[])",
+                    "className=" + className + ", name=" + name + ", loaderName="
+                    + loaderName + ", params=" + objects(params)
+                    + ", signature=" + strings(signature));
 
             final MarshalledObject<Object[]> sParams =
                     new MarshalledObject<Object[]>(params);
             final ClassLoader old = pushDefaultClassLoader();
             try {
                 return connection.createMBean(className,
-                                             name,
-                                             loaderName,
-                                             sParams,
-                                             signature,
-                                             delegationSubject);
+                        name,
+                        loaderName,
+                        sParams,
+                        signature,
+                        delegationSubject);
             } catch (IOException ioe) {
                 communicatorAdmin.gotIOException(ioe);
 
                 return connection.createMBean(className,
-                                             name,
-                                             loaderName,
-                                             sParams,
-                                             signature,
-                                             delegationSubject);
+                        name,
+                        loaderName,
+                        sParams,
+                        signature,
+                        delegationSubject);
             } finally {
                 popDefaultClassLoader(old);
             }
         }
 
         public void unregisterMBean(ObjectName name)
-                throws InstanceNotFoundException,
-                       MBeanRegistrationException,
-                       IOException {
+        throws InstanceNotFoundException,
+                MBeanRegistrationException,
+                IOException {
             if (logger.debugOn())
                 logger.debug("unregisterMBean", "name=" + name);
 
@@ -837,8 +837,8 @@
         }
 
         public ObjectInstance getObjectInstance(ObjectName name)
-                throws InstanceNotFoundException,
-                       IOException {
+        throws InstanceNotFoundException,
+                IOException {
             if (logger.debugOn())
                 logger.debug("getObjectInstance", "name=" + name);
 
@@ -855,10 +855,10 @@
         }
 
         public Set<ObjectInstance> queryMBeans(ObjectName name,
-                                               QueryExp query)
-            throws IOException {
+                QueryExp query)
+                throws IOException {
             if (logger.debugOn()) logger.debug("queryMBeans",
-                                   "name=" + name + ", query=" + query);
+                    "name=" + name + ", query=" + query);
 
             final MarshalledObject<QueryExp> sQuery =
                     new MarshalledObject<QueryExp>(query);
@@ -875,10 +875,10 @@
         }
 
         public Set<ObjectName> queryNames(ObjectName name,
-                                          QueryExp query)
+                QueryExp query)
                 throws IOException {
             if (logger.debugOn()) logger.debug("queryNames",
-                                   "name=" + name + ", query=" + query);
+                    "name=" + name + ", query=" + query);
 
             final MarshalledObject<QueryExp> sQuery =
                     new MarshalledObject<QueryExp>(query);
@@ -895,7 +895,7 @@
         }
 
         public boolean isRegistered(ObjectName name)
-                throws IOException {
+        throws IOException {
             if (logger.debugOn())
                 logger.debug("isRegistered", "name=" + name);
 
@@ -912,7 +912,7 @@
         }
 
         public Integer getMBeanCount()
-                throws IOException {
+        throws IOException {
             if (logger.debugOn()) logger.debug("getMBeanCount", "");
 
             final ClassLoader old = pushDefaultClassLoader();
@@ -928,53 +928,53 @@
         }
 
         public Object getAttribute(ObjectName name,
-                                   String attribute)
+                String attribute)
                 throws MBeanException,
-                       AttributeNotFoundException,
-                       InstanceNotFoundException,
-                       ReflectionException,
-                       IOException {
+                AttributeNotFoundException,
+                InstanceNotFoundException,
+                ReflectionException,
+                IOException {
             if (logger.debugOn()) logger.debug("getAttribute",
-                                   "name=" + name + ", attribute="
-                                   + attribute);
+                    "name=" + name + ", attribute="
+                    + attribute);
 
             final ClassLoader old = pushDefaultClassLoader();
             try {
                 return connection.getAttribute(name,
-                                              attribute,
-                                              delegationSubject);
+                        attribute,
+                        delegationSubject);
             } catch (IOException ioe) {
                 communicatorAdmin.gotIOException(ioe);
 
                 return connection.getAttribute(name,
-                                              attribute,
-                                              delegationSubject);
+                        attribute,
+                        delegationSubject);
             } finally {
                 popDefaultClassLoader(old);
             }
         }
 
         public AttributeList getAttributes(ObjectName name,
-                                           String[] attributes)
+                String[] attributes)
                 throws InstanceNotFoundException,
-                       ReflectionException,
-                       IOException {
+                ReflectionException,
+                IOException {
             if (logger.debugOn()) logger.debug("getAttributes",
-                                   "name=" + name + ", attributes="
-                                   + strings(attributes));
+                    "name=" + name + ", attributes="
+                    + strings(attributes));
 
             final ClassLoader old = pushDefaultClassLoader();
             try {
                 return connection.getAttributes(name,
-                                               attributes,
-                                               delegationSubject);
+                        attributes,
+                        delegationSubject);
 
             } catch (IOException ioe) {
                 communicatorAdmin.gotIOException(ioe);
 
                 return connection.getAttributes(name,
-                                               attributes,
-                                               delegationSubject);
+                        attributes,
+                        delegationSubject);
             } finally {
                 popDefaultClassLoader(old);
             }
@@ -982,20 +982,20 @@
 
 
         public void setAttribute(ObjectName name,
-                                 Attribute attribute)
-            throws InstanceNotFoundException,
-                   AttributeNotFoundException,
-                   InvalidAttributeValueException,
-                   MBeanException,
-                   ReflectionException,
-                   IOException {
+                Attribute attribute)
+                throws InstanceNotFoundException,
+                AttributeNotFoundException,
+                InvalidAttributeValueException,
+                MBeanException,
+                ReflectionException,
+                IOException {
 
             if (logger.debugOn()) logger.debug("setAttribute",
-                                   "name=" + name + ", attribute="
-                                   + attribute);
+                    "name=" + name + ", attribute="
+                    + attribute);
 
             final MarshalledObject<Attribute> sAttribute =
-                new MarshalledObject<Attribute>(attribute);
+                    new MarshalledObject<Attribute>(attribute);
             final ClassLoader old = pushDefaultClassLoader();
             try {
                 connection.setAttribute(name, sAttribute, delegationSubject);
@@ -1009,28 +1009,28 @@
         }
 
         public AttributeList setAttributes(ObjectName name,
-                                           AttributeList attributes)
-            throws InstanceNotFoundException,
-                   ReflectionException,
-                   IOException {
+                AttributeList attributes)
+                throws InstanceNotFoundException,
+                ReflectionException,
+                IOException {
 
             if (logger.debugOn()) logger.debug("setAttributes",
-                                   "name=" + name + ", attributes="
-                                   + attributes);
+                    "name=" + name + ", attributes="
+                    + attributes);
 
             final MarshalledObject<AttributeList> sAttributes =
-                new MarshalledObject<AttributeList>(attributes);
+                    new MarshalledObject<AttributeList>(attributes);
             final ClassLoader old = pushDefaultClassLoader();
             try {
                 return connection.setAttributes(name,
-                                               sAttributes,
-                                               delegationSubject);
+                        sAttributes,
+                        delegationSubject);
             } catch (IOException ioe) {
                 communicatorAdmin.gotIOException(ioe);
 
                 return connection.setAttributes(name,
-                                               sAttributes,
-                                               delegationSubject);
+                        sAttributes,
+                        delegationSubject);
             } finally {
                 popDefaultClassLoader(old);
             }
@@ -1038,37 +1038,37 @@
 
 
         public Object invoke(ObjectName name,
-                             String operationName,
-                             Object params[],
-                             String signature[])
+                String operationName,
+                Object params[],
+                String signature[])
                 throws InstanceNotFoundException,
-                       MBeanException,
-                       ReflectionException,
-                       IOException {
+                MBeanException,
+                ReflectionException,
+                IOException {
 
             if (logger.debugOn()) logger.debug("invoke",
-                                   "name=" + name
-                                   + ", operationName=" + operationName
-                                   + ", params=" + objects(params)
-                                   + ", signature=" + strings(signature));
+                    "name=" + name
+                    + ", operationName=" + operationName
+                    + ", params=" + objects(params)
+                    + ", signature=" + strings(signature));
 
             final MarshalledObject<Object[]> sParams =
                     new MarshalledObject<Object[]>(params);
             final ClassLoader old = pushDefaultClassLoader();
             try {
                 return connection.invoke(name,
-                                        operationName,
-                                        sParams,
-                                        signature,
-                                        delegationSubject);
+                        operationName,
+                        sParams,
+                        signature,
+                        delegationSubject);
             } catch (IOException ioe) {
                 communicatorAdmin.gotIOException(ioe);
 
                 return connection.invoke(name,
-                                        operationName,
-                                        sParams,
-                                        signature,
-                                        delegationSubject);
+                        operationName,
+                        sParams,
+                        signature,
+                        delegationSubject);
             } finally {
                 popDefaultClassLoader(old);
             }
@@ -1076,7 +1076,7 @@
 
 
         public String getDefaultDomain()
-                throws IOException {
+        throws IOException {
             if (logger.debugOn()) logger.debug("getDefaultDomain", "");
 
             final ClassLoader old = pushDefaultClassLoader();
@@ -1107,10 +1107,10 @@
         }
 
         public MBeanInfo getMBeanInfo(ObjectName name)
-                throws InstanceNotFoundException,
-                       IntrospectionException,
-                       ReflectionException,
-                       IOException {
+        throws InstanceNotFoundException,
+                IntrospectionException,
+                ReflectionException,
+                IOException {
 
             if (logger.debugOn()) logger.debug("getMBeanInfo", "name=" + name);
             final ClassLoader old = pushDefaultClassLoader();
@@ -1127,41 +1127,41 @@
 
 
         public boolean isInstanceOf(ObjectName name,
-                                    String className)
+                String className)
                 throws InstanceNotFoundException,
-                       IOException {
+                IOException {
             if (logger.debugOn())
                 logger.debug("isInstanceOf", "name=" + name +
-                             ", className=" + className);
+                        ", className=" + className);
 
             final ClassLoader old = pushDefaultClassLoader();
             try {
                 return connection.isInstanceOf(name,
-                                              className,
-                                              delegationSubject);
+                        className,
+                        delegationSubject);
             } catch (IOException ioe) {
                 communicatorAdmin.gotIOException(ioe);
 
                 return connection.isInstanceOf(name,
-                                              className,
-                                              delegationSubject);
+                        className,
+                        delegationSubject);
             } finally {
                 popDefaultClassLoader(old);
             }
         }
 
         public void addNotificationListener(ObjectName name,
-                                            ObjectName listener,
-                                            NotificationFilter filter,
-                                            Object handback)
+                ObjectName listener,
+                NotificationFilter filter,
+                Object handback)
                 throws InstanceNotFoundException,
-                       IOException {
+                IOException {
 
             if (logger.debugOn())
                 logger.debug("addNotificationListener" +
-                       "(ObjectName,ObjectName,NotificationFilter,Object)",
-                       "name=" + name + ", listener=" + listener
-                       + ", filter=" + filter + ", handback=" + handback);
+                        "(ObjectName,ObjectName,NotificationFilter,Object)",
+                        "name=" + name + ", listener=" + listener
+                        + ", filter=" + filter + ", handback=" + handback);
 
             final MarshalledObject<NotificationFilter> sFilter =
                     new MarshalledObject<NotificationFilter>(filter);
@@ -1170,64 +1170,64 @@
             final ClassLoader old = pushDefaultClassLoader();
             try {
                 connection.addNotificationListener(name,
-                                                  listener,
-                                                  sFilter,
-                                                  sHandback,
-                                                  delegationSubject);
+                        listener,
+                        sFilter,
+                        sHandback,
+                        delegationSubject);
             } catch (IOException ioe) {
                 communicatorAdmin.gotIOException(ioe);
 
                 connection.addNotificationListener(name,
-                                                  listener,
-                                                  sFilter,
-                                                  sHandback,
-                                                  delegationSubject);
+                        listener,
+                        sFilter,
+                        sHandback,
+                        delegationSubject);
             } finally {
                 popDefaultClassLoader(old);
             }
         }
 
         public void removeNotificationListener(ObjectName name,
-                                               ObjectName listener)
+                ObjectName listener)
                 throws InstanceNotFoundException,
-                       ListenerNotFoundException,
-                       IOException {
+                ListenerNotFoundException,
+                IOException {
 
             if (logger.debugOn()) logger.debug("removeNotificationListener" +
-                                   "(ObjectName,ObjectName)",
-                                   "name=" + name
-                                   + ", listener=" + listener);
+                    "(ObjectName,ObjectName)",
+                    "name=" + name
+                    + ", listener=" + listener);
 
             final ClassLoader old = pushDefaultClassLoader();
             try {
                 connection.removeNotificationListener(name,
-                                                     listener,
-                                                     delegationSubject);
+                        listener,
+                        delegationSubject);
             } catch (IOException ioe) {
                 communicatorAdmin.gotIOException(ioe);
 
                 connection.removeNotificationListener(name,
-                                                     listener,
-                                                     delegationSubject);
+                        listener,
+                        delegationSubject);
             } finally {
                 popDefaultClassLoader(old);
             }
         }
 
         public void removeNotificationListener(ObjectName name,
-                                               ObjectName listener,
-                                               NotificationFilter filter,
-                                               Object handback)
+                ObjectName listener,
+                NotificationFilter filter,
+                Object handback)
                 throws InstanceNotFoundException,
-                       ListenerNotFoundException,
-                       IOException {
+                ListenerNotFoundException,
+                IOException {
             if (logger.debugOn())
                 logger.debug("removeNotificationListener" +
-                      "(ObjectName,ObjectName,NotificationFilter,Object)",
-                      "name=" + name
-                      + ", listener=" + listener
-                      + ", filter=" + filter
-                      + ", handback=" + handback);
+                        "(ObjectName,ObjectName,NotificationFilter,Object)",
+                        "name=" + name
+                        + ", listener=" + listener
+                        + ", filter=" + filter
+                        + ", handback=" + handback);
 
             final MarshalledObject<NotificationFilter> sFilter =
                     new MarshalledObject<NotificationFilter>(filter);
@@ -1236,18 +1236,18 @@
             final ClassLoader old = pushDefaultClassLoader();
             try {
                 connection.removeNotificationListener(name,
-                                                     listener,
-                                                     sFilter,
-                                                     sHandback,
-                                                     delegationSubject);
+                        listener,
+                        sFilter,
+                        sHandback,
+                        delegationSubject);
             } catch (IOException ioe) {
                 communicatorAdmin.gotIOException(ioe);
 
                 connection.removeNotificationListener(name,
-                                                     listener,
-                                                     sFilter,
-                                                     sHandback,
-                                                     delegationSubject);
+                        listener,
+                        sFilter,
+                        sHandback,
+                        delegationSubject);
             } finally {
                 popDefaultClassLoader(old);
             }
@@ -1256,34 +1256,34 @@
         // Specific Notification Handle ----------------------------------
 
         public void addNotificationListener(ObjectName name,
-                                            NotificationListener listener,
-                                            NotificationFilter filter,
-                                            Object handback)
+                NotificationListener listener,
+                NotificationFilter filter,
+                Object handback)
                 throws InstanceNotFoundException,
-                       IOException {
+                IOException {
 
             final boolean debug = logger.debugOn();
 
             if (debug)
                 logger.debug("addNotificationListener" +
-                             "(ObjectName,NotificationListener,"+
-                             "NotificationFilter,Object)",
-                             "name=" + name
-                             + ", listener=" + listener
-                             + ", filter=" + filter
-                             + ", handback=" + handback);
+                        "(ObjectName,NotificationListener,"+
+                        "NotificationFilter,Object)",
+                        "name=" + name
+                        + ", listener=" + listener
+                        + ", filter=" + filter
+                        + ", handback=" + handback);
 
             final Integer listenerID =
-                addListenerWithSubject(name,
-                                       new MarshalledObject<NotificationFilter>(filter),
-                                       delegationSubject,true);
+                    addListenerWithSubject(name,
+                    new MarshalledObject<NotificationFilter>(filter),
+                    delegationSubject,true);
             rmiNotifClient.addNotificationListener(listenerID, name, listener,
-                                                   filter, handback,
-                                                   delegationSubject);
+                    filter, handback,
+                    delegationSubject);
         }
 
         public void removeNotificationListener(ObjectName name,
-                                               NotificationListener listener)
+                NotificationListener listener)
                 throws InstanceNotFoundException,
                 ListenerNotFoundException,
                 IOException {
@@ -1291,28 +1291,28 @@
             final boolean debug = logger.debugOn();
 
             if (debug) logger.debug("removeNotificationListener"+
-                             "(ObjectName,NotificationListener)",
-                             "name=" + name
-                             + ", listener=" + listener);
+                    "(ObjectName,NotificationListener)",
+                    "name=" + name
+                    + ", listener=" + listener);
 
             final Integer[] ret =
-                rmiNotifClient.removeNotificationListener(name, listener);
+                    rmiNotifClient.removeNotificationListener(name, listener);
 
             if (debug) logger.debug("removeNotificationListener",
-                             "listenerIDs=" + objects(ret));
+                    "listenerIDs=" + objects(ret));
 
             final ClassLoader old = pushDefaultClassLoader();
 
             try {
                 connection.removeNotificationListeners(name,
-                                                       ret,
-                                                       delegationSubject);
+                        ret,
+                        delegationSubject);
             } catch (IOException ioe) {
                 communicatorAdmin.gotIOException(ioe);
 
                 connection.removeNotificationListeners(name,
-                                                       ret,
-                                                       delegationSubject);
+                        ret,
+                        delegationSubject);
             } finally {
                 popDefaultClassLoader(old);
             }
@@ -1320,41 +1320,41 @@
         }
 
         public void removeNotificationListener(ObjectName name,
-                                           NotificationListener listener,
-                                           NotificationFilter filter,
-                                           Object handback)
-            throws InstanceNotFoundException,
-                   ListenerNotFoundException,
-                   IOException {
+                NotificationListener listener,
+                NotificationFilter filter,
+                Object handback)
+                throws InstanceNotFoundException,
+                ListenerNotFoundException,
+                IOException {
             final boolean debug = logger.debugOn();
 
             if (debug)
                 logger.debug("removeNotificationListener"+
-                      "(ObjectName,NotificationListener,"+
-                      "NotificationFilter,Object)",
-                      "name=" + name
-                      + ", listener=" + listener
-                      + ", filter=" + filter
-                      + ", handback=" + handback);
+                        "(ObjectName,NotificationListener,"+
+                        "NotificationFilter,Object)",
+                        "name=" + name
+                        + ", listener=" + listener
+                        + ", filter=" + filter
+                        + ", handback=" + handback);
 
             final Integer ret =
-                rmiNotifClient.removeNotificationListener(name, listener,
-                                                         filter, handback);
+                    rmiNotifClient.removeNotificationListener(name, listener,
+                    filter, handback);
 
             if (debug) logger.debug("removeNotificationListener",
-                             "listenerID=" + ret);
+                    "listenerID=" + ret);
 
             final ClassLoader old = pushDefaultClassLoader();
             try {
                 connection.removeNotificationListeners(name,
-                                                       new Integer[] {ret},
-                                                       delegationSubject);
+                        new Integer[] {ret},
+                        delegationSubject);
             } catch (IOException ioe) {
                 communicatorAdmin.gotIOException(ioe);
 
                 connection.removeNotificationListeners(name,
-                                                       new Integer[] {ret},
-                                                       delegationSubject);
+                        new Integer[] {ret},
+                        delegationSubject);
             } finally {
                 popDefaultClassLoader(old);
             }
@@ -1369,16 +1369,16 @@
         }
 
         protected NotificationResult fetchNotifs(long clientSequenceNumber,
-                                                 int maxNotifications,
-                                                 long timeout)
+                int maxNotifications,
+                long timeout)
                 throws IOException, ClassNotFoundException {
             IOException org;
 
             while (true) { // used for a successful re-connection
                 try {
                     return connection.fetchNotifications(clientSequenceNumber,
-                                                         maxNotifications,
-                                                         timeout);
+                            maxNotifications,
+                            timeout);
                 } catch (IOException ioe) {
                     org = ioe;
 
@@ -1417,7 +1417,7 @@
                    clear we can do much better.  */
                 if (ume.detail instanceof WriteAbortedException) {
                     WriteAbortedException wae =
-                        (WriteAbortedException) ume.detail;
+                            (WriteAbortedException) ume.detail;
                     if (wae.detail instanceof IOException)
                         throw (IOException) wae.detail;
                 }
@@ -1435,11 +1435,11 @@
         }
 
         protected Integer addListenerForMBeanRemovedNotif()
-                throws IOException, InstanceNotFoundException {
+        throws IOException, InstanceNotFoundException {
             NotificationFilterSupport clientFilter =
-                new NotificationFilterSupport();
+                    new NotificationFilterSupport();
             clientFilter.enableType(
-                MBeanServerNotification.UNREGISTRATION_NOTIFICATION);
+                    MBeanServerNotification.UNREGISTRATION_NOTIFICATION);
             MarshalledObject<NotificationFilter> sFilter =
                 new MarshalledObject<NotificationFilter>(clientFilter);
 
@@ -1451,36 +1451,36 @@
             final Subject[] subjects = new Subject[] {null};
             try {
                 listenerIDs =
-                    connection.addNotificationListeners(names,
-                                                        filters,
-                                                        subjects);
+                        connection.addNotificationListeners(names,
+                        filters,
+                        subjects);
 
             } catch (IOException ioe) {
                 communicatorAdmin.gotIOException(ioe);
 
                 listenerIDs =
-                    connection.addNotificationListeners(names,
-                                                        filters,
-                                                        subjects);
+                        connection.addNotificationListeners(names,
+                        filters,
+                        subjects);
             }
             return listenerIDs[0];
         }
 
         protected void removeListenerForMBeanRemovedNotif(Integer id)
-                throws IOException, InstanceNotFoundException,
-                       ListenerNotFoundException {
+        throws IOException, InstanceNotFoundException,
+                ListenerNotFoundException {
             try {
                 connection.removeNotificationListeners(
-                                             MBeanServerDelegate.DELEGATE_NAME,
-                                             new Integer[] {id},
-                                             null);
+                        MBeanServerDelegate.DELEGATE_NAME,
+                        new Integer[] {id},
+                        null);
             } catch (IOException ioe) {
                 communicatorAdmin.gotIOException(ioe);
 
                 connection.removeNotificationListeners(
-                                             MBeanServerDelegate.DELEGATE_NAME,
-                                             new Integer[] {id},
-                                             null);
+                        MBeanServerDelegate.DELEGATE_NAME,
+                        new Integer[] {id},
+                        null);
             }
 
         }
@@ -1530,7 +1530,7 @@
                     // we should close the connection,
                     // but send a failed notif at first
                     final Notification failedNotif =
-                        new JMXConnectionNotification(
+                            new JMXConnectionNotification(
                             JMXConnectionNotification.FAILED,
                             this,
                             connectionId,
@@ -1559,7 +1559,7 @@
                    will throw a ServerException at client side which wraps this
                    UnmarshalException.
                    No failed notif here.
-                */
+                 */
                 Throwable tt = ((ServerException)ioe).detail;
 
                 if (tt instanceof IOException) {
@@ -1600,11 +1600,11 @@
 
                 for (i=0;i<len;i++) {
                     clis[i] = new ClientListenerInfo(ids[i],
-                                                     names[i],
-                                                     listeners[i],
-                                                     filters[i],
-                                                     handbacks[i],
-                                                     subjects[i]);
+                            names[i],
+                            listeners[i],
+                            filters[i],
+                            handbacks[i],
+                            subjects[i]);
                 }
 
                 rmiNotifClient.postReconnection(clis);
@@ -1623,15 +1623,15 @@
                             false);
 
                     clis[j++] = new ClientListenerInfo(id,
-                                                       names[i],
-                                                       listeners[i],
-                                                       filters[i],
-                                                       handbacks[i],
-                                                       subjects[i]);
+                            names[i],
+                            listeners[i],
+                            filters[i],
+                            handbacks[i],
+                            subjects[i]);
                 } catch (InstanceNotFoundException infe) {
                     logger.warning("reconnectNotificationListeners",
-                                   "Can't reconnect listener for " +
-                                   names[i]);
+                            "Can't reconnect listener for " +
+                            names[i]);
                 }
             }
 
@@ -1647,7 +1647,7 @@
         protected void checkConnection() throws IOException {
             if (logger.debugOn())
                 logger.debug("RMIClientCommunicatorAdmin-checkConnection",
-                             "Calling the method getDefaultDomain.");
+                        "Calling the method getDefaultDomain.");
 
             connection.getDefaultDomain(null);
         }
@@ -1677,12 +1677,12 @@
             connectionId = getConnectionId();
 
             Notification reconnectedNotif =
-                new JMXConnectionNotification(JMXConnectionNotification.OPENED,
-                                              this,
-                                              connectionId,
-                                              clientNotifSeqNo++,
-                                              "Reconnected to server",
-                                              null);
+                    new JMXConnectionNotification(JMXConnectionNotification.OPENED,
+                    this,
+                    connectionId,
+                    clientNotifSeqNo++,
+                    "Reconnected to server",
+                    null);
             sendNotification(reconnectedNotif);
 
         }
@@ -1692,7 +1692,7 @@
                 close();
             } catch (IOException ioe) {
                 logger.warning("RMIClientCommunicatorAdmin-doStop",
-                               "Failed to call the method close():" + ioe);
+                        "Failed to call the method close():" + ioe);
                 logger.debug("RMIClientCommunicatorAdmin-doStop",ioe);
             }
         }
@@ -1785,15 +1785,15 @@
             final Object orb = environment.get(EnvHelp.DEFAULT_ORB);
             if (orb != null && !(orb instanceof  ORB))
                 throw new IllegalArgumentException(EnvHelp.DEFAULT_ORB +
-                          " must be an instance of org.omg.CORBA.ORB.");
+                        " must be an instance of org.omg.CORBA.ORB.");
             if (orb != null) return (ORB)orb;
         }
         final ORB orb =
-            (RMIConnector.orb==null)?null:RMIConnector.orb.get();
+                (RMIConnector.orb==null)?null:RMIConnector.orb.get();
         if (orb != null) return orb;
 
         final ORB newOrb =
-            ORB.init((String[])null, (Properties)null);
+                ORB.init((String[])null, (Properties)null);
         RMIConnector.orb = new WeakReference<ORB>(newOrb);
         return newOrb;
     }
@@ -1810,11 +1810,11 @@
      * @see #RMIConnector(RMIServer,Map)
      **/
     private void readObject(java.io.ObjectInputStream s)
-        throws IOException, ClassNotFoundException  {
+    throws IOException, ClassNotFoundException  {
         s.defaultReadObject();
 
         if (rmiServer == null && jmxServiceURL == null) throw new
-          InvalidObjectException("rmiServer and jmxServiceURL both null");
+                InvalidObjectException("rmiServer and jmxServiceURL both null");
 
         initTransients();
     }
@@ -1851,9 +1851,9 @@
      * @see #RMIConnector(RMIServer,Map)
      **/
     private void writeObject(java.io.ObjectOutputStream s)
-        throws IOException {
+    throws IOException {
         if (rmiServer == null && jmxServiceURL == null) throw new
-          InvalidObjectException("rmiServer and jmxServiceURL both null.");
+                InvalidObjectException("rmiServer and jmxServiceURL both null.");
         connectStub(this.rmiServer,env);
         s.defaultWriteObject();
     }
@@ -1892,21 +1892,21 @@
     //--------------------------------------------------------------------
 
     private static void checkStub(Remote stub,
-                                  Class<?> stubClass) {
+            Class<?> stubClass) {
 
         // Check remote stub is from the expected class.
         //
         if (stub.getClass() != stubClass) {
             if (!Proxy.isProxyClass(stub.getClass())) {
                 throw new SecurityException(
-                          "Expecting a " + stubClass.getName() + " stub!");
+                        "Expecting a " + stubClass.getName() + " stub!");
             } else {
                 InvocationHandler handler = Proxy.getInvocationHandler(stub);
                 if (handler.getClass() != RemoteObjectInvocationHandler.class)
                     throw new SecurityException(
-                              "Expecting a dynamic proxy instance with a " +
-                              RemoteObjectInvocationHandler.class.getName() +
-                              " invocation handler!");
+                            "Expecting a dynamic proxy instance with a " +
+                            RemoteObjectInvocationHandler.class.getName() +
+                            " invocation handler!");
                 else
                     stub = (Remote) handler;
             }
@@ -1918,8 +1918,8 @@
         RemoteRef ref = ((RemoteObject)stub).getRef();
         if (ref.getClass() != UnicastRef2.class)
             throw new SecurityException(
-                      "Expecting a " + UnicastRef2.class.getName() +
-                      " remote reference in stub!");
+                    "Expecting a " + UnicastRef2.class.getName() +
+                    " remote reference in stub!");
 
         // Check RMIClientSocketFactory in stub is from the expected class
         // "javax.rmi.ssl.SslRMIClientSocketFactory".
@@ -1928,8 +1928,8 @@
         RMIClientSocketFactory csf = liveRef.getClientSocketFactory();
         if (csf == null || csf.getClass() != SslRMIClientSocketFactory.class)
             throw new SecurityException(
-                  "Expecting a " + SslRMIClientSocketFactory.class.getName() +
-                  " RMI client socket factory in stub!");
+                    "Expecting a " + SslRMIClientSocketFactory.class.getName() +
+                    " RMI client socket factory in stub!");
     }
 
     //--------------------------------------------------------------------
@@ -1937,8 +1937,8 @@
     //--------------------------------------------------------------------
 
     private RMIServer findRMIServer(JMXServiceURL directoryURL,
-                                    Map<String, Object> environment)
-        throws NamingException, IOException {
+            Map<String, Object> environment)
+            throws NamingException, IOException {
         final boolean isIiop = RMIConnectorServer.isIiopURL(directoryURL,true);
         if (isIiop) {
             // Make sure java.naming.corba.orb is in the Map.
@@ -1956,7 +1956,7 @@
             return findRMIServerIIOP(path.substring(5,end), environment, isIiop);
         else {
             final String msg = "URL path must begin with /jndi/ or /stub/ " +
-                "or /ior/: " + path;
+                    "or /ior/: " + path;
             throw new MalformedURLException(msg);
         }
     }
@@ -1975,8 +1975,8 @@
      * @exception NamingException if the stub couldn't be found.
      **/
     private RMIServer findRMIServerJNDI(String jndiURL, Map<String, ?> env,
-                                        boolean isIiop)
-        throws NamingException {
+            boolean isIiop)
+            throws NamingException {
 
         InitialContext ctx = new InitialContext(EnvHelp.mapToHashtable(env));
 
@@ -1997,11 +1997,11 @@
     private static RMIServer narrowIIOPServer(Object objref) {
         try {
             return (RMIServer)
-                PortableRemoteObject.narrow(objref, RMIServer.class);
+            PortableRemoteObject.narrow(objref, RMIServer.class);
         } catch (ClassCastException e) {
             if (logger.traceOn())
                 logger.trace("narrowIIOPServer","Failed to narrow objref=" +
-                      objref + ": " + e);
+                        objref + ": " + e);
             if (logger.debugOn()) logger.debug("narrowIIOPServer",e);
             return null;
         }
@@ -2010,7 +2010,7 @@
     private RMIServer findRMIServerIIOP(String ior, Map<String, ?> env, boolean isIiop) {
         // could forbid "rmi:" URL here -- but do we need to?
         final ORB orb = (ORB)
-            env.get(EnvHelp.DEFAULT_ORB);
+        env.get(EnvHelp.DEFAULT_ORB);
         final Object stub = orb.string_to_object(ior);
         return (RMIServer) PortableRemoteObject.narrow(stub, RMIServer.class);
     }
@@ -2023,15 +2023,15 @@
             serialized = base64ToByteArray(base64);
         } catch (IllegalArgumentException e) {
             throw new MalformedURLException("Bad BASE64 encoding: " +
-                                            e.getMessage());
+                    e.getMessage());
         }
         final ByteArrayInputStream bin = new ByteArrayInputStream(serialized);
 
         final ClassLoader loader = EnvHelp.resolveClientClassLoader(env);
         final ObjectInputStream oin =
-            (loader == null) ?
-                new ObjectInputStream(bin) :
-                new ObjectInputStreamWithLoader(bin, loader);
+                (loader == null) ?
+                    new ObjectInputStream(bin) :
+                    new ObjectInputStreamWithLoader(bin, loader);
         final Object stub;
         try {
             stub = oin.readObject();
@@ -2044,7 +2044,7 @@
     private static final class ObjectInputStreamWithLoader
             extends ObjectInputStream {
         ObjectInputStreamWithLoader(InputStream in, ClassLoader cl)
-                throws IOException {
+        throws IOException {
             super(in);
             this.loader = cl;
         }
@@ -2127,40 +2127,40 @@
         RMIServer.class.getName() + "Impl_Stub";
     private static final Class<?> rmiServerImplStubClass;
     private static final String rmiConnectionImplStubClassName =
-        RMIConnection.class.getName() + "Impl_Stub";
+            RMIConnection.class.getName() + "Impl_Stub";
     private static final Class<?> rmiConnectionImplStubClass;
     private static final String pRefClassName =
         "com.sun.jmx.remote.internal.PRef";
     private static final Constructor<?> proxyRefConstructor;
     static {
         final String pRefByteCodeString =
-            "\312\376\272\276\0\0\0.\0\27\12\0\5\0\15\11\0\4\0\16\13\0\17\0"+
-            "\20\7\0\21\7\0\22\1\0\6<init>\1\0\36(Ljava/rmi/server/RemoteRef;"+
-            ")V\1\0\4Code\1\0\6invoke\1\0S(Ljava/rmi/Remote;Ljava/lang/reflec"+
-            "t/Method;[Ljava/lang/Object;J)Ljava/lang/Object;\1\0\12Exception"+
-            "s\7\0\23\14\0\6\0\7\14\0\24\0\25\7\0\26\14\0\11\0\12\1\0\40com/"+
-            "sun/jmx/remote/internal/PRef\1\0$com/sun/jmx/remote/internal/Pr"+
-            "oxyRef\1\0\23java/lang/Exception\1\0\3ref\1\0\33Ljava/rmi/serve"+
-            "r/RemoteRef;\1\0\31java/rmi/server/RemoteRef\0!\0\4\0\5\0\0\0\0"+
-            "\0\2\0\1\0\6\0\7\0\1\0\10\0\0\0\22\0\2\0\2\0\0\0\6*+\267\0\1\261"+
-            "\0\0\0\0\0\1\0\11\0\12\0\2\0\10\0\0\0\33\0\6\0\6\0\0\0\17*\264\0"+
-            "\2+,-\26\4\271\0\3\6\0\260\0\0\0\0\0\13\0\0\0\4\0\1\0\14\0\0";
+                "\312\376\272\276\0\0\0.\0\27\12\0\5\0\15\11\0\4\0\16\13\0\17\0"+
+                "\20\7\0\21\7\0\22\1\0\6<init>\1\0\36(Ljava/rmi/server/RemoteRef;"+
+                ")V\1\0\4Code\1\0\6invoke\1\0S(Ljava/rmi/Remote;Ljava/lang/reflec"+
+                "t/Method;[Ljava/lang/Object;J)Ljava/lang/Object;\1\0\12Exception"+
+                "s\7\0\23\14\0\6\0\7\14\0\24\0\25\7\0\26\14\0\11\0\12\1\0\40com/"+
+                "sun/jmx/remote/internal/PRef\1\0$com/sun/jmx/remote/internal/Pr"+
+                "oxyRef\1\0\23java/lang/Exception\1\0\3ref\1\0\33Ljava/rmi/serve"+
+                "r/RemoteRef;\1\0\31java/rmi/server/RemoteRef\0!\0\4\0\5\0\0\0\0"+
+                "\0\2\0\1\0\6\0\7\0\1\0\10\0\0\0\22\0\2\0\2\0\0\0\6*+\267\0\1\261"+
+                "\0\0\0\0\0\1\0\11\0\12\0\2\0\10\0\0\0\33\0\6\0\6\0\0\0\17*\264\0"+
+                "\2+,-\26\4\271\0\3\6\0\260\0\0\0\0\0\13\0\0\0\4\0\1\0\14\0\0";
         final byte[] pRefByteCode =
-            NoCallStackClassLoader.stringToBytes(pRefByteCodeString);
+                NoCallStackClassLoader.stringToBytes(pRefByteCodeString);
         PrivilegedExceptionAction<Constructor<?>> action =
                 new PrivilegedExceptionAction<Constructor<?>>() {
             public Constructor<?> run() throws Exception {
                 Class thisClass = RMIConnector.class;
                 ClassLoader thisLoader = thisClass.getClassLoader();
                 ProtectionDomain thisProtectionDomain =
-                    thisClass.getProtectionDomain();
+                        thisClass.getProtectionDomain();
                 String[] otherClassNames = {ProxyRef.class.getName()};
                 ClassLoader cl =
-                    new NoCallStackClassLoader(pRefClassName,
-                                               pRefByteCode,
-                                               otherClassNames,
-                                               thisLoader,
-                                               thisProtectionDomain);
+                        new NoCallStackClassLoader(pRefClassName,
+                        pRefByteCode,
+                        otherClassNames,
+                        thisLoader,
+                        thisProtectionDomain);
                 Class<?> c = cl.loadClass(pRefClassName);
                 return c.getConstructor(RemoteRef.class);
             }
@@ -2171,8 +2171,8 @@
             serverStubClass = Class.forName(rmiServerImplStubClassName);
         } catch (Exception e) {
             logger.error("<clinit>",
-                         "Failed to instantiate " +
-                         rmiServerImplStubClassName + ": " + e);
+                    "Failed to instantiate " +
+                    rmiServerImplStubClassName + ": " + e);
             logger.debug("<clinit>",e);
             serverStubClass = null;
         }
@@ -2185,8 +2185,8 @@
             constr = (Constructor<?>) AccessController.doPrivileged(action);
         } catch (Exception e) {
             logger.error("<clinit>",
-                         "Failed to initialize proxy reference constructor "+
-                         "for " + rmiConnectionImplStubClassName + ": " + e);
+                    "Failed to initialize proxy reference constructor "+
+                    "for " + rmiConnectionImplStubClassName + ": " + e);
             logger.debug("<clinit>",e);
             stubClass = null;
             constr = null;
@@ -2196,9 +2196,9 @@
     }
 
     private static RMIConnection shadowJrmpStub(RemoteObject stub)
-            throws InstantiationException, IllegalAccessException,
-                   InvocationTargetException, ClassNotFoundException,
-                   NoSuchMethodException {
+    throws InstantiationException, IllegalAccessException,
+            InvocationTargetException, ClassNotFoundException,
+            NoSuchMethodException {
         RemoteRef ref = stub.getRef();
         RemoteRef proxyRef = (RemoteRef)
             proxyRefConstructor.newInstance(new Object[] {ref});
@@ -2206,7 +2206,7 @@
             rmiConnectionImplStubClass.getConstructor(RemoteRef.class);
         Object[] args = {proxyRef};
         RMIConnection proxyStub = (RMIConnection)
-            rmiConnectionImplStubConstructor.newInstance(args);
+        rmiConnectionImplStubConstructor.newInstance(args);
         return proxyStub;
     }
 
@@ -2326,55 +2326,55 @@
 
      */
     private static final String iiopConnectionStubClassName =
-        "org.omg.stub.javax.management.remote.rmi._RMIConnection_Stub";
+            "org.omg.stub.javax.management.remote.rmi._RMIConnection_Stub";
     private static final String proxyStubClassName =
-        "com.sun.jmx.remote.internal.ProxyStub";
+            "com.sun.jmx.remote.internal.ProxyStub";
     private static final String pInputStreamClassName =
         "com.sun.jmx.remote.internal.PInputStream";
     private static final Class<?> proxyStubClass;
     static {
         final String proxyStubByteCodeString =
-            "\312\376\272\276\0\0\0.\0)\12\0\14\0\26\7\0\27\12\0\14\0\30\12"+
-            "\0\2\0\31\7\0\32\12\0\5\0\33\12\0\5\0\34\12\0\5\0\35\12\0\2\0"+
-            "\36\12\0\14\0\37\7\0\40\7\0!\1\0\6<init>\1\0\3()V\1\0\4Code\1"+
-            "\0\7_invoke\1\0K(Lorg/omg/CORBA/portable/OutputStream;)Lorg/o"+
-            "mg/CORBA/portable/InputStream;\1\0\12Exceptions\7\0\"\1\0\15_"+
-            "releaseReply\1\0'(Lorg/omg/CORBA/portable/InputStream;)V\14\0"+
-            "\15\0\16\1\0(com/sun/jmx/remote/internal/PInputStream\14\0\20"+
-            "\0\21\14\0\15\0\25\1\0+org/omg/CORBA/portable/ApplicationExce"+
-            "ption\14\0#\0$\14\0%\0&\14\0\15\0'\14\0(\0$\14\0\24\0\25\1\0%"+
-            "com/sun/jmx/remote/internal/ProxyStub\1\0<org/omg/stub/javax/"+
-            "management/remote/rmi/_RMIConnection_Stub\1\0)org/omg/CORBA/p"+
-            "ortable/RemarshalException\1\0\16getInputStream\1\0&()Lorg/om"+
-            "g/CORBA/portable/InputStream;\1\0\5getId\1\0\24()Ljava/lang/S"+
-            "tring;\1\09(Ljava/lang/String;Lorg/omg/CORBA/portable/InputSt"+
-            "ream;)V\1\0\25getProxiedInputStream\0!\0\13\0\14\0\0\0\0\0\3\0"+
-            "\1\0\15\0\16\0\1\0\17\0\0\0\21\0\1\0\1\0\0\0\5*\267\0\1\261\0"+
-            "\0\0\0\0\1\0\20\0\21\0\2\0\17\0\0\0;\0\4\0\4\0\0\0'\273\0\2Y*"+
-            "+\267\0\3\267\0\4\260M\273\0\2Y,\266\0\6\267\0\4N\273\0\5Y,\266"+
-            "\0\7-\267\0\10\277\0\1\0\0\0\14\0\15\0\5\0\0\0\22\0\0\0\6\0\2"+
-            "\0\5\0\23\0\1\0\24\0\25\0\1\0\17\0\0\0\36\0\2\0\2\0\0\0\22+\306"+
-            "\0\13+\300\0\2\266\0\11L*+\267\0\12\261\0\0\0\0\0\0";
+                "\312\376\272\276\0\0\0.\0)\12\0\14\0\26\7\0\27\12\0\14\0\30\12"+
+                "\0\2\0\31\7\0\32\12\0\5\0\33\12\0\5\0\34\12\0\5\0\35\12\0\2\0"+
+                "\36\12\0\14\0\37\7\0\40\7\0!\1\0\6<init>\1\0\3()V\1\0\4Code\1"+
+                "\0\7_invoke\1\0K(Lorg/omg/CORBA/portable/OutputStream;)Lorg/o"+
+                "mg/CORBA/portable/InputStream;\1\0\12Exceptions\7\0\"\1\0\15_"+
+                "releaseReply\1\0'(Lorg/omg/CORBA/portable/InputStream;)V\14\0"+
+                "\15\0\16\1\0(com/sun/jmx/remote/internal/PInputStream\14\0\20"+
+                "\0\21\14\0\15\0\25\1\0+org/omg/CORBA/portable/ApplicationExce"+
+                "ption\14\0#\0$\14\0%\0&\14\0\15\0'\14\0(\0$\14\0\24\0\25\1\0%"+
+                "com/sun/jmx/remote/internal/ProxyStub\1\0<org/omg/stub/javax/"+
+                "management/remote/rmi/_RMIConnection_Stub\1\0)org/omg/CORBA/p"+
+                "ortable/RemarshalException\1\0\16getInputStream\1\0&()Lorg/om"+
+                "g/CORBA/portable/InputStream;\1\0\5getId\1\0\24()Ljava/lang/S"+
+                "tring;\1\09(Ljava/lang/String;Lorg/omg/CORBA/portable/InputSt"+
+                "ream;)V\1\0\25getProxiedInputStream\0!\0\13\0\14\0\0\0\0\0\3\0"+
+                "\1\0\15\0\16\0\1\0\17\0\0\0\21\0\1\0\1\0\0\0\5*\267\0\1\261\0"+
+                "\0\0\0\0\1\0\20\0\21\0\2\0\17\0\0\0;\0\4\0\4\0\0\0'\273\0\2Y*"+
+                "+\267\0\3\267\0\4\260M\273\0\2Y,\266\0\6\267\0\4N\273\0\5Y,\266"+
+                "\0\7-\267\0\10\277\0\1\0\0\0\14\0\15\0\5\0\0\0\22\0\0\0\6\0\2"+
+                "\0\5\0\23\0\1\0\24\0\25\0\1\0\17\0\0\0\36\0\2\0\2\0\0\0\22+\306"+
+                "\0\13+\300\0\2\266\0\11L*+\267\0\12\261\0\0\0\0\0\0";
         final String pInputStreamByteCodeString =
-            "\312\376\272\276\0\0\0.\0\36\12\0\7\0\17\11\0\6\0\20\12\0\21\0"+
-            "\22\12\0\6\0\23\12\0\24\0\25\7\0\26\7\0\27\1\0\6<init>\1\0'(L"+
-            "org/omg/CORBA/portable/InputStream;)V\1\0\4Code\1\0\10read_an"+
-            "y\1\0\25()Lorg/omg/CORBA/Any;\1\0\12read_value\1\0)(Ljava/lan"+
-            "g/Class;)Ljava/io/Serializable;\14\0\10\0\11\14\0\30\0\31\7\0"+
-            "\32\14\0\13\0\14\14\0\33\0\34\7\0\35\14\0\15\0\16\1\0(com/sun"+
-            "/jmx/remote/internal/PInputStream\1\0,com/sun/jmx/remote/inte"+
-            "rnal/ProxyInputStream\1\0\2in\1\0$Lorg/omg/CORBA/portable/Inp"+
-            "utStream;\1\0\"org/omg/CORBA/portable/InputStream\1\0\6narrow"+
-            "\1\0*()Lorg/omg/CORBA_2_3/portable/InputStream;\1\0&org/omg/C"+
-            "ORBA_2_3/portable/InputStream\0!\0\6\0\7\0\0\0\0\0\3\0\1\0\10"+
-            "\0\11\0\1\0\12\0\0\0\22\0\2\0\2\0\0\0\6*+\267\0\1\261\0\0\0\0"+
-            "\0\1\0\13\0\14\0\1\0\12\0\0\0\24\0\1\0\1\0\0\0\10*\264\0\2\266"+
-            "\0\3\260\0\0\0\0\0\1\0\15\0\16\0\1\0\12\0\0\0\25\0\2\0\2\0\0\0"+
-            "\11*\266\0\4+\266\0\5\260\0\0\0\0\0\0";
+                "\312\376\272\276\0\0\0.\0\36\12\0\7\0\17\11\0\6\0\20\12\0\21\0"+
+                "\22\12\0\6\0\23\12\0\24\0\25\7\0\26\7\0\27\1\0\6<init>\1\0'(L"+
+                "org/omg/CORBA/portable/InputStream;)V\1\0\4Code\1\0\10read_an"+
+                "y\1\0\25()Lorg/omg/CORBA/Any;\1\0\12read_value\1\0)(Ljava/lan"+
+                "g/Class;)Ljava/io/Serializable;\14\0\10\0\11\14\0\30\0\31\7\0"+
+                "\32\14\0\13\0\14\14\0\33\0\34\7\0\35\14\0\15\0\16\1\0(com/sun"+
+                "/jmx/remote/internal/PInputStream\1\0,com/sun/jmx/remote/inte"+
+                "rnal/ProxyInputStream\1\0\2in\1\0$Lorg/omg/CORBA/portable/Inp"+
+                "utStream;\1\0\"org/omg/CORBA/portable/InputStream\1\0\6narrow"+
+                "\1\0*()Lorg/omg/CORBA_2_3/portable/InputStream;\1\0&org/omg/C"+
+                "ORBA_2_3/portable/InputStream\0!\0\6\0\7\0\0\0\0\0\3\0\1\0\10"+
+                "\0\11\0\1\0\12\0\0\0\22\0\2\0\2\0\0\0\6*+\267\0\1\261\0\0\0\0"+
+                "\0\1\0\13\0\14\0\1\0\12\0\0\0\24\0\1\0\1\0\0\0\10*\264\0\2\266"+
+                "\0\3\260\0\0\0\0\0\1\0\15\0\16\0\1\0\12\0\0\0\25\0\2\0\2\0\0\0"+
+                "\11*\266\0\4+\266\0\5\260\0\0\0\0\0\0";
         final byte[] proxyStubByteCode =
-            NoCallStackClassLoader.stringToBytes(proxyStubByteCodeString);
+                NoCallStackClassLoader.stringToBytes(proxyStubByteCodeString);
         final byte[] pInputStreamByteCode =
-            NoCallStackClassLoader.stringToBytes(pInputStreamByteCodeString);
+                NoCallStackClassLoader.stringToBytes(pInputStreamByteCodeString);
         final String[] classNames={proxyStubClassName, pInputStreamClassName};
         final byte[][] byteCodes = {proxyStubByteCode, pInputStreamByteCode};
         final String[] otherClassNames = {
@@ -2388,13 +2388,13 @@
                 Class thisClass = RMIConnector.class;
                 ClassLoader thisLoader = thisClass.getClassLoader();
                 ProtectionDomain thisProtectionDomain =
-                    thisClass.getProtectionDomain();
+                        thisClass.getProtectionDomain();
                 ClassLoader cl =
-                    new NoCallStackClassLoader(classNames,
-                                               byteCodes,
-                                               otherClassNames,
-                                               thisLoader,
-                                               thisProtectionDomain);
+                        new NoCallStackClassLoader(classNames,
+                        byteCodes,
+                        otherClassNames,
+                        thisLoader,
+                        thisProtectionDomain);
                 return cl.loadClass(proxyStubClassName);
             }
         };
@@ -2403,7 +2403,7 @@
             stubClass = AccessController.doPrivileged(action);
         } catch (Exception e) {
             logger.error("<clinit>",
-                   "Unexpected exception making shadow IIOP stub class: "+e);
+                    "Unexpected exception making shadow IIOP stub class: "+e);
             logger.debug("<clinit>",e);
             stubClass = null;
         }
@@ -2411,15 +2411,15 @@
     }
 
     private static RMIConnection shadowIiopStub(Stub stub)
-            throws InstantiationException, IllegalAccessException {
+    throws InstantiationException, IllegalAccessException {
         Stub proxyStub = (Stub) proxyStubClass.newInstance();
         proxyStub._set_delegate(stub._get_delegate());
         return (RMIConnection) proxyStub;
     }
 
     private static RMIConnection getConnection(RMIServer server,
-                                               Object credentials,
-                                               boolean checkStub)
+            Object credentials,
+            boolean checkStub)
             throws IOException {
         RMIConnection c = server.newClient(credentials);
         if (checkStub) checkStub(c, rmiConnectionImplStubClass);
@@ -2429,14 +2429,14 @@
             if (c.getClass().getName().equals(iiopConnectionStubClassName))
                 return shadowIiopStub((Stub) c);
             logger.trace("getConnection",
-                         "Did not wrap " + c.getClass() + " to foil " +
-                         "stack search for classes: class loading semantics " +
-                         "may be incorrect");
+                    "Did not wrap " + c.getClass() + " to foil " +
+                    "stack search for classes: class loading semantics " +
+                    "may be incorrect");
         } catch (Exception e) {
             logger.error("getConnection",
-                         "Could not wrap " + c.getClass() + " to foil " +
-                         "stack search for classes: class loading semantics " +
-                         "may be incorrect: " + e);
+                    "Could not wrap " + c.getClass() + " to foil " +
+                    "stack search for classes: class loading semantics " +
+                    "may be incorrect: " + e);
             logger.debug("getConnection",e);
             // so just return the original stub, which will work for all
             // but the most exotic class loading situations
@@ -2449,7 +2449,7 @@
         int numGroups = sLen/4;
         if (4*numGroups != sLen)
             throw new IllegalArgumentException(
-                "String length must be a multiple of four.");
+                    "String length must be a multiple of four.");
         int missingBytesInLastGroup = 0;
         int numFullGroups = numGroups;
         if (sLen != 0) {
@@ -2535,21 +2535,21 @@
         final ClassLoader old =  t.getContextClassLoader();
         if (defaultClassLoader != null)
             AccessController.doPrivileged(new PrivilegedAction<Void>() {
-                    public Void run() {
-                        t.setContextClassLoader(defaultClassLoader);
-                        return null;
-                    }
-                });
-        return old;
+                public Void run() {
+                    t.setContextClassLoader(defaultClassLoader);
+                    return null;
+                }
+            });
+            return old;
     }
 
     private void popDefaultClassLoader(final ClassLoader old) {
         AccessController.doPrivileged(new PrivilegedAction<Void>() {
-                public Void run() {
-                    Thread.currentThread().setContextClassLoader(old);
-                    return null;
-                }
-            });
+            public Void run() {
+                Thread.currentThread().setContextClassLoader(old);
+                return null;
+            }
+        });
     }
 
     //--------------------------------------------------------------------
--- a/src/share/classes/javax/management/remote/rmi/RMIConnectorServer.java	Thu Nov 06 12:12:39 2008 -0500
+++ b/src/share/classes/javax/management/remote/rmi/RMIConnectorServer.java	Fri Nov 07 11:48:07 2008 +0100
@@ -383,7 +383,7 @@
         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 @@
         else
             rmiServer = newServer();
 
-        rmiServer.setMBeanServer(getSystemMBeanServer());
+        rmiServer.setMBeanServer(getSystemMBeanServerForwarder());
         rmiServer.setDefaultClassLoader(defaultClassLoader);
         rmiServer.setRMIConnectorServer(this);
         rmiServer.export();
@@ -592,31 +592,6 @@
         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 @@
        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);
--- a/test/javax/management/Introspector/AnnotationTest.java	Thu Nov 06 12:12:39 2008 -0500
+++ b/test/javax/management/Introspector/AnnotationTest.java	Fri Nov 07 11:48:07 2008 +0100
@@ -39,7 +39,8 @@
 
 /*
   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 @@
     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 @@
 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 @@
         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 @@
                                 });
 
     @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 @@
         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 @@
     }
 
     @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 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 @@
         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)
+        else
             throw new Exception("TEST FAILED: " + failed);
-        else
-            System.out.println("Test disabled until 6221321 implemented");
     }
 
     private static void check(MBeanServer mbs, ObjectName on) throws Exception {
@@ -295,151 +433,4 @@
         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);
-            }
-        }
-    }
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/javax/management/context/ContextForwarderTest.java	Fri Nov 07 11:48:07 2008 +0100
@@ -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);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/javax/management/context/ContextTest.java	Fri Nov 07 11:48:07 2008 +0100
@@ -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);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/javax/management/context/LocaleAwareBroadcasterTest.java	Fri Nov 07 11:48:07 2008 +0100
@@ -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;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/javax/management/context/LocaleTest.java	Fri Nov 07 11:48:07 2008 +0100
@@ -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);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/javax/management/context/LocalizableTest.java	Fri Nov 07 11:48:07 2008 +0100
@@ -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;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/javax/management/context/RemoteContextTest.java	Fri Nov 07 11:48:07 2008 +0100
@@ -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);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/javax/management/context/localizable/MBeanDescriptions.properties	Fri Nov 07 11:48:07 2008 +0100
@@ -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.
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/javax/management/context/localizable/MBeanDescriptions_fr.java	Fri Nov 07 11:48:07 2008 +0100
@@ -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},
+        };
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/javax/management/context/localizable/Whatsit.java	Fri Nov 07 11:48:07 2008 +0100
@@ -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;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/javax/management/context/localizable/WhatsitMBean.java	Fri Nov 07 11:48:07 2008 +0100
@@ -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);
+}
--- a/test/javax/management/eventService/CustomForwarderTest.java	Thu Nov 06 12:12:39 2008 -0500
+++ b/test/javax/management/eventService/CustomForwarderTest.java	Fri Nov 07 11:48:07 2008 +0100
@@ -200,8 +200,7 @@
 
     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
--- a/test/javax/management/eventService/EventClientExecutorTest.java	Thu Nov 06 12:12:39 2008 -0500
+++ b/test/javax/management/eventService/EventClientExecutorTest.java	Fri Nov 07 11:48:07 2008 +0100
@@ -65,8 +65,7 @@
                 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);
--- a/test/javax/management/eventService/EventManagerTest.java	Thu Nov 06 12:12:39 2008 -0500
+++ b/test/javax/management/eventService/EventManagerTest.java	Fri Nov 07 11:48:07 2008 +0100
@@ -98,7 +98,7 @@
         succeed &= test(new EventClient(ecd,
                 new RMIPushEventRelay(ecd),
                 null, null,
-                EventClient.DEFAULT_LEASE_TIMEOUT));
+                EventClient.DEFAULT_REQUESTED_LEASE_TIME));
 
         conn.close();
         server.stop();
--- a/test/javax/management/eventService/ListenerTest.java	Thu Nov 06 12:12:39 2008 -0500
+++ b/test/javax/management/eventService/ListenerTest.java	Fri Nov 07 11:48:07 2008 +0100
@@ -99,7 +99,7 @@
         succeed &= test(new EventClient(ecd,
                 new RMIPushEventRelay(ecd),
                 null, null,
-                EventClient.DEFAULT_LEASE_TIMEOUT));
+                EventClient.DEFAULT_REQUESTED_LEASE_TIME));
 
         conn.close();
         server.stop();
--- a/test/javax/management/eventService/NotSerializableNotifTest.java	Thu Nov 06 12:12:39 2008 -0500
+++ b/test/javax/management/eventService/NotSerializableNotifTest.java	Fri Nov 07 11:48:07 2008 +0100
@@ -95,7 +95,7 @@
                 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();
--- a/test/javax/management/eventService/UsingEventService.java	Thu Nov 06 12:12:39 2008 -0500
+++ b/test/javax/management/eventService/UsingEventService.java	Fri Nov 07 11:48:07 2008 +0100
@@ -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
--- a/test/javax/management/namespace/EventWithNamespaceControlTest.java	Thu Nov 06 12:12:39 2008 -0500
+++ b/test/javax/management/namespace/EventWithNamespaceControlTest.java	Fri Nov 07 11:48:07 2008 +0100
@@ -85,6 +85,7 @@
         }
     }
 
+    @Override
     public Map<String, ?> getServerMap() {
         Map<String, ?> retValue = Collections.emptyMap();
         return retValue;
--- a/test/javax/management/namespace/JMXNamespaceSecurityTest.java	Thu Nov 06 12:12:39 2008 -0500
+++ b/test/javax/management/namespace/JMXNamespaceSecurityTest.java	Fri Nov 07 11:48:07 2008 +0100
@@ -51,6 +51,7 @@
 import javax.management.namespace.JMXNamespace;
 import javax.management.namespace.JMXNamespaces;
 import javax.management.remote.JMXConnectorServer;
+import javax.management.ClientContext;
 
 /**
  *
--- a/test/javax/management/namespace/JMXNamespaceViewTest.java	Thu Nov 06 12:12:39 2008 -0500
+++ b/test/javax/management/namespace/JMXNamespaceViewTest.java	Fri Nov 07 11:48:07 2008 +0100
@@ -42,6 +42,7 @@
 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 @@
  */
 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
      */
--- a/test/javax/management/namespace/JMXRemoteTargetNamespace.java	Thu Nov 06 12:12:39 2008 -0500
+++ b/test/javax/management/namespace/JMXRemoteTargetNamespace.java	Fri Nov 07 11:48:07 2008 +0100
@@ -68,13 +68,7 @@
 
     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 @@
     }
 
     @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;
     }
 
 
--- a/test/javax/management/namespace/NamespaceNotificationsTest.java	Thu Nov 06 12:12:39 2008 -0500
+++ b/test/javax/management/namespace/NamespaceNotificationsTest.java	Fri Nov 07 11:48:07 2008 +0100
@@ -206,18 +206,10 @@
             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());
--- a/test/javax/management/namespace/NullDomainObjectNameTest.java	Thu Nov 06 12:12:39 2008 -0500
+++ b/test/javax/management/namespace/NullDomainObjectNameTest.java	Fri Nov 07 11:48:07 2008 +0100
@@ -155,7 +155,7 @@
             // namespace.
             //
             RoutingServerProxy proxy =
-                    new RoutingServerProxy(sub, "", "faked", false);
+                    new RoutingServerProxy(sub, "", "faked", true);
 
             // These should fail because the ObjectName doesn't start
             // with "faked//"
--- a/test/javax/management/namespace/NullObjectNameTest.java	Thu Nov 06 12:12:39 2008 -0500
+++ b/test/javax/management/namespace/NullObjectNameTest.java	Fri Nov 07 11:48:07 2008 +0100
@@ -162,7 +162,7 @@
             // 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()+
--- a/test/javax/management/openmbean/CompositeDataStringTest.java	Thu Nov 06 12:12:39 2008 -0500
+++ b/test/javax/management/openmbean/CompositeDataStringTest.java	Fri Nov 07 11:48:07 2008 +0100
@@ -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 {
--- a/test/javax/management/remote/mandatory/connectorServer/ForwarderChainTest.java	Thu Nov 06 12:12:39 2008 -0500
+++ b/test/javax/management/remote/mandatory/connectorServer/ForwarderChainTest.java	Fri Nov 07 11:48:07 2008 +0100
@@ -86,16 +86,20 @@
         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 @@
     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 @@
         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 @@
         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 @@
                     }
                     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 @@
     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);
--- a/test/javax/management/remote/mandatory/connectorServer/StandardForwardersTest.java	Thu Nov 06 12:12:39 2008 -0500
+++ b/test/javax/management/remote/mandatory/connectorServer/StandardForwardersTest.java	Fri Nov 07 11:48:07 2008 +0100
@@ -26,6 +26,7 @@
 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 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 @@
         }
         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 @@
 
     // 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) {
--- a/test/javax/management/remote/mandatory/provider/ProviderTest.java	Thu Nov 06 12:12:39 2008 -0500
+++ b/test/javax/management/remote/mandatory/provider/ProviderTest.java	Fri Nov 07 11:48:07 2008 +0100
@@ -46,6 +46,8 @@
 /*
  * 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 @@
 
         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);
         }
--- a/test/javax/management/remote/mandatory/subjectDelegation/SimpleStandard.java	Thu Nov 06 12:12:39 2008 -0500
+++ b/test/javax/management/remote/mandatory/subjectDelegation/SimpleStandard.java	Fri Nov 07 11:48:07 2008 +0100
@@ -75,7 +75,7 @@
      * @return the current value of the "State" attribute.
      */
     public String getState() {
-        checkSubject();
+        checkSubject("getState");
         return state;
     }
 
@@ -85,7 +85,7 @@
      * @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 @@
      * @return the current value of the "NbChanges" attribute.
      */
     public int getNbChanges() {
-        checkSubject();
+        checkSubject("getNbChanges");
         return nbChanges;
     }
 
@@ -106,7 +106,7 @@
      * attributes of the "SimpleStandard" standard MBean.
      */
     public void reset() {
-        checkSubject();
+        checkSubject("reset");
         AttributeChangeNotification acn =
             new AttributeChangeNotification(this,
                                             0,
@@ -149,18 +149,18 @@
      * 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);
     }