From ab227cb6710d61179ffbc13c20d0ecbd339f8491 Mon Sep 17 00:00:00 2001 From: Eamonn McManus Date: Fri, 7 Nov 2008 11:48:07 +0100 Subject: [PATCH] 5072267: A way to communicate client context such as locale to the JMX server Support for client contexts and also for localization of descriptions Reviewed-by: dfuchs --- .../com/sun/jmx/defaults/ServiceName.java | 4 +- .../com/sun/jmx/event/EventParams.java | 2 +- .../com/sun/jmx/event/LeaseManager.java | 6 +- .../jmx/interceptor/SingleMBeanForwarder.java | 52 +- .../sun/jmx/mbeanserver/JmxMBeanServer.java | 4 +- .../sun/jmx/namespace/JMXNamespaceUtils.java | 354 ------ .../sun/jmx/namespace/ObjectNameRouter.java | 7 - .../jmx/namespace/RoutingConnectionProxy.java | 33 +- .../com/sun/jmx/namespace/RoutingProxy.java | 72 +- .../sun/jmx/namespace/RoutingServerProxy.java | 30 +- .../remote/util/EventClientConnection.java | 4 +- .../javax/management/ClientContext.java | 1091 +++++++++++++++++ .../classes/javax/management/Descriptor.java | 32 +- .../share/classes/javax/management/JMX.java | 32 +- .../classes/javax/management/MBeanInfo.java | 384 ++++++ .../management/MBeanServerNotification.java | 38 +- .../javax/management/Notification.java | 44 +- .../javax/management/event/EventClient.java | 56 +- .../management/event/EventClientDelegate.java | 58 +- .../event/EventClientDelegateMBean.java | 7 +- .../javax/management/event/EventRelay.java | 1 - .../javax/management/event/package-info.java | 4 +- .../management/namespace/JMXNamespaces.java | 40 +- .../namespace/JMXRemoteNamespace.java | 249 ++-- .../remote/JMXConnectorFactory.java | 19 +- .../management/remote/JMXConnectorServer.java | 384 +++--- .../remote/JMXConnectorServerMBean.java | 4 +- .../management/remote/rmi/RMIConnector.java | 924 +++++++------- .../remote/rmi/RMIConnectorServer.java | 32 +- .../Introspector/AnnotationTest.java | 321 +++-- .../context/ContextForwarderTest.java | 103 ++ .../javax/management/context/ContextTest.java | 534 ++++++++ .../context/LocaleAwareBroadcasterTest.java | 328 +++++ .../javax/management/context/LocaleTest.java | 140 +++ .../management/context/LocalizableTest.java | 192 +++ .../management/context/RemoteContextTest.java | 496 ++++++++ .../localizable/MBeanDescriptions.properties | 9 + .../localizable/MBeanDescriptions_fr.java | 42 + .../context/localizable/Whatsit.java | 59 + .../context/localizable/WhatsitMBean.java | 53 + .../eventService/CustomForwarderTest.java | 3 +- .../eventService/EventClientExecutorTest.java | 3 +- .../eventService/EventManagerTest.java | 2 +- .../management/eventService/ListenerTest.java | 2 +- .../NotSerializableNotifTest.java | 2 +- .../eventService/UsingEventService.java | 23 + .../EventWithNamespaceControlTest.java | 1 + .../namespace/JMXNamespaceSecurityTest.java | 1 + .../namespace/JMXNamespaceViewTest.java | 6 +- .../namespace/JMXRemoteTargetNamespace.java | 22 +- .../namespace/NamespaceNotificationsTest.java | 16 +- .../namespace/NullDomainObjectNameTest.java | 2 +- .../namespace/NullObjectNameTest.java | 2 +- .../openmbean/CompositeDataStringTest.java | 8 +- .../connectorServer/ForwarderChainTest.java | 31 +- .../StandardForwardersTest.java | 28 +- .../mandatory/provider/ProviderTest.java | 12 +- .../subjectDelegation/SimpleStandard.java | 14 +- 58 files changed, 4875 insertions(+), 1547 deletions(-) delete mode 100644 jdk/src/share/classes/com/sun/jmx/namespace/JMXNamespaceUtils.java create mode 100644 jdk/src/share/classes/javax/management/ClientContext.java create mode 100644 jdk/test/javax/management/context/ContextForwarderTest.java create mode 100644 jdk/test/javax/management/context/ContextTest.java create mode 100644 jdk/test/javax/management/context/LocaleAwareBroadcasterTest.java create mode 100644 jdk/test/javax/management/context/LocaleTest.java create mode 100644 jdk/test/javax/management/context/LocalizableTest.java create mode 100644 jdk/test/javax/management/context/RemoteContextTest.java create mode 100644 jdk/test/javax/management/context/localizable/MBeanDescriptions.properties create mode 100644 jdk/test/javax/management/context/localizable/MBeanDescriptions_fr.java create mode 100644 jdk/test/javax/management/context/localizable/Whatsit.java create mode 100644 jdk/test/javax/management/context/localizable/WhatsitMBean.java diff --git a/jdk/src/share/classes/com/sun/jmx/defaults/ServiceName.java b/jdk/src/share/classes/com/sun/jmx/defaults/ServiceName.java index f70730b8d21..a01ea89e258 100644 --- a/jdk/src/share/classes/com/sun/jmx/defaults/ServiceName.java +++ b/jdk/src/share/classes/com/sun/jmx/defaults/ServiceName.java @@ -69,9 +69,9 @@ public class ServiceName { /** * The version of the JMX specification implemented by this product. *
- * The value is 1.4. + * The value is 2.0. */ - 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. diff --git a/jdk/src/share/classes/com/sun/jmx/event/EventParams.java b/jdk/src/share/classes/com/sun/jmx/event/EventParams.java index 7d875e1385b..c0e76634cbd 100644 --- a/jdk/src/share/classes/com/sun/jmx/event/EventParams.java +++ b/jdk/src/share/classes/com/sun/jmx/event/EventParams.java @@ -41,7 +41,7 @@ public class EventParams { @SuppressWarnings("cast") // cast for jdk 1.5 public static long getLeaseTimeout() { - long timeout = EventClient.DEFAULT_LEASE_TIMEOUT; + long timeout = EventClient.DEFAULT_REQUESTED_LEASE_TIME; try { final GetPropertyAction act = new GetPropertyAction(DEFAULT_LEASE_TIMEOUT); diff --git a/jdk/src/share/classes/com/sun/jmx/event/LeaseManager.java b/jdk/src/share/classes/com/sun/jmx/event/LeaseManager.java index 09a7a03a42d..2db6fea146b 100644 --- a/jdk/src/share/classes/com/sun/jmx/event/LeaseManager.java +++ b/jdk/src/share/classes/com/sun/jmx/event/LeaseManager.java @@ -29,6 +29,7 @@ import com.sun.jmx.remote.util.ClassLogger; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; /** @@ -143,9 +144,10 @@ public class LeaseManager { private final Runnable callback; private ScheduledFuture scheduled; // If null, the lease has expired. + private static final ThreadFactory threadFactory = + new DaemonThreadFactory("JMX LeaseManager %d"); private final ScheduledExecutorService executor - = Executors.newScheduledThreadPool(1, - new DaemonThreadFactory("JMX LeaseManager %d")); + = Executors.newScheduledThreadPool(1, threadFactory); private static final ClassLogger logger = new ClassLogger("javax.management.event", "LeaseManager"); diff --git a/jdk/src/share/classes/com/sun/jmx/interceptor/SingleMBeanForwarder.java b/jdk/src/share/classes/com/sun/jmx/interceptor/SingleMBeanForwarder.java index 4a47d4ce3aa..cfb40092557 100644 --- a/jdk/src/share/classes/com/sun/jmx/interceptor/SingleMBeanForwarder.java +++ b/jdk/src/share/classes/com/sun/jmx/interceptor/SingleMBeanForwarder.java @@ -55,9 +55,19 @@ import javax.management.namespace.JMXNamespaces; import javax.management.namespace.MBeanServerSupport; import javax.management.remote.IdentityMBeanServerForwarder; +/** + *

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.

+ */ public class SingleMBeanForwarder extends IdentityMBeanServerForwarder { private final ObjectName mbeanName; + private final boolean visible; private DynamicMBean mbean; private MBeanServer mbeanMBS = new MBeanServerSupport() { @@ -85,10 +95,20 @@ public class SingleMBeanForwarder extends IdentityMBeanServerForwarder { return null; } + // This will only be called if mbeanName has an empty domain. + // In that case a getAttribute (e.g.) of that name will have the + // domain replaced by MBeanServerSupport with the default domain, + // so we must be sure that the default domain is empty too. + @Override + public String getDefaultDomain() { + return mbeanName.getDomain(); + } }; - public SingleMBeanForwarder(ObjectName mbeanName, DynamicMBean mbean) { + public SingleMBeanForwarder( + ObjectName mbeanName, DynamicMBean mbean, boolean visible) { this.mbeanName = mbeanName; + this.visible = visible; setSingleMBean(mbean); } @@ -213,8 +233,10 @@ public class SingleMBeanForwarder extends IdentityMBeanServerForwarder { @Override public String[] getDomains() { - TreeSet domainSet = - new TreeSet(Arrays.asList(super.getDomains())); + String[] domains = super.getDomains(); + if (!visible) + return domains; + TreeSet domainSet = new TreeSet(Arrays.asList(domains)); domainSet.add(mbeanName.getDomain()); return domainSet.toArray(new String[domainSet.size()]); } @@ -222,7 +244,7 @@ public class SingleMBeanForwarder extends IdentityMBeanServerForwarder { @Override public Integer getMBeanCount() { Integer count = super.getMBeanCount(); - if (!super.isRegistered(mbeanName)) + if (visible && !super.isRegistered(mbeanName)) count++; return count; } @@ -284,7 +306,7 @@ public class SingleMBeanForwarder extends IdentityMBeanServerForwarder { */ private boolean applies(ObjectName pattern) { // we know pattern is not null. - if (!pattern.apply(mbeanName)) + if (!visible || !pattern.apply(mbeanName)) return false; final String dompat = pattern.getDomain(); @@ -306,10 +328,12 @@ public class SingleMBeanForwarder extends IdentityMBeanServerForwarder { @Override public Set queryMBeans(ObjectName name, QueryExp query) { Set names = super.queryMBeans(name, query); - if (name == null || applies(name) ) { - // Don't assume mbs.queryNames returns a writable set. - names = Util.cloneSet(names); - names.addAll(mbeanMBS.queryMBeans(name, query)); + if (visible) { + if (name == null || applies(name) ) { + // Don't assume mbs.queryNames returns a writable set. + names = Util.cloneSet(names); + names.addAll(mbeanMBS.queryMBeans(name, query)); + } } return names; } @@ -317,10 +341,12 @@ public class SingleMBeanForwarder extends IdentityMBeanServerForwarder { @Override public Set queryNames(ObjectName name, QueryExp query) { Set 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; } diff --git a/jdk/src/share/classes/com/sun/jmx/mbeanserver/JmxMBeanServer.java b/jdk/src/share/classes/com/sun/jmx/mbeanserver/JmxMBeanServer.java index 14ee0ac5279..4fc25e91278 100644 --- a/jdk/src/share/classes/com/sun/jmx/mbeanserver/JmxMBeanServer.java +++ b/jdk/src/share/classes/com/sun/jmx/mbeanserver/JmxMBeanServer.java @@ -122,7 +122,7 @@ public final class JmxMBeanServer * {@link javax.management.MBeanServerFactory#newMBeanServer(java.lang.String)} * instead. *

- * 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. * * @param domain The default domain name used by this MBeanServer. @@ -239,7 +239,7 @@ public final class JmxMBeanServer this.mBeanServerDelegateObject = delegate; this.outerShell = outer; - final Repository repository = new Repository(domain,fairLock); + final Repository repository = new Repository(domain); this.mbsInterceptor = new NamespaceDispatchInterceptor(outer, delegate, instantiator, repository); diff --git a/jdk/src/share/classes/com/sun/jmx/namespace/JMXNamespaceUtils.java b/jdk/src/share/classes/com/sun/jmx/namespace/JMXNamespaceUtils.java deleted file mode 100644 index 49f875144f2..00000000000 --- a/jdk/src/share/classes/com/sun/jmx/namespace/JMXNamespaceUtils.java +++ /dev/null @@ -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. - *

- * This API is a Sun internal API and is subject to changes without notice. - *

- * @since 1.7 - */ -public final class JMXNamespaceUtils { - - /** - * A logger for this class. - **/ - private static final Logger LOG = JmxProperties.NAMESPACE_LOGGER; - - - private static Map newWeakHashMap() { - return new WeakHashMap(); - } - - /** There are no instances of this class */ - private JMXNamespaceUtils() { - } - - // returns un unmodifiable view of a map. - public static Map unmodifiableMap(Map 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. - *

- * 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 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 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 checkNonNull(T parameter, String name) { - if (parameter == null) - throw new IllegalArgumentException(name+" must not be null"); - return parameter; - } - - -} diff --git a/jdk/src/share/classes/com/sun/jmx/namespace/ObjectNameRouter.java b/jdk/src/share/classes/com/sun/jmx/namespace/ObjectNameRouter.java index 3c7065c478e..7d668169fc7 100644 --- a/jdk/src/share/classes/com/sun/jmx/namespace/ObjectNameRouter.java +++ b/jdk/src/share/classes/com/sun/jmx/namespace/ObjectNameRouter.java @@ -49,11 +49,6 @@ public class ObjectNameRouter { final int tlen; final boolean identity; - - public ObjectNameRouter(String targetDirName) { - this(targetDirName,null); - } - /** Creates a new instance of ObjectNameRouter */ public ObjectNameRouter(final String remove, final String add) { this.targetPrefix = (remove==null?"":remove); @@ -186,6 +181,4 @@ public class ObjectNameRouter { b.append(NAMESPACE_SEPARATOR); return b.toString(); } - - } diff --git a/jdk/src/share/classes/com/sun/jmx/namespace/RoutingConnectionProxy.java b/jdk/src/share/classes/com/sun/jmx/namespace/RoutingConnectionProxy.java index 443c80f2ae5..b09bc84365c 100644 --- a/jdk/src/share/classes/com/sun/jmx/namespace/RoutingConnectionProxy.java +++ b/jdk/src/share/classes/com/sun/jmx/namespace/RoutingConnectionProxy.java @@ -31,7 +31,6 @@ import java.util.logging.Level; import java.util.logging.Logger; import javax.management.MBeanServerConnection; -import javax.management.namespace.JMXNamespaces; /** @@ -57,22 +56,14 @@ public class RoutingConnectionProxy private static final Logger LOG = JmxProperties.NAMESPACE_LOGGER; - /** - * Creates a new instance of RoutingConnectionProxy - */ - public RoutingConnectionProxy(MBeanServerConnection source, - String sourceDir) { - this(source,sourceDir,"",false); - } - /** * Creates a new instance of RoutingConnectionProxy */ public RoutingConnectionProxy(MBeanServerConnection source, String sourceDir, String targetDir, - boolean forwardsContext) { - super(source,sourceDir,targetDir,forwardsContext); + boolean probe) { + super(source, sourceDir, targetDir, probe); if (LOG.isLoggable(Level.FINER)) LOG.finer("RoutingConnectionProxy for " + getSourceNamespace() + @@ -85,15 +76,13 @@ public class RoutingConnectionProxy final String sourceNs = getSourceNamespace(); String wrapped = String.valueOf(source()); if ("".equals(targetNs)) { - if (forwardsContext) - wrapped = "ClientContext.withDynamicContext("+wrapped+")"; return "JMXNamespaces.narrowToNamespace("+ wrapped+", \""+ sourceNs+"\")"; } return this.getClass().getSimpleName()+"("+wrapped+", \""+ sourceNs+"\", \""+ - targetNs+"\", "+forwardsContext+")"; + targetNs+"\")"; } static final RoutingProxyFactory @@ -102,22 +91,16 @@ public class RoutingConnectionProxy () { 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); } } diff --git a/jdk/src/share/classes/com/sun/jmx/namespace/RoutingProxy.java b/jdk/src/share/classes/com/sun/jmx/namespace/RoutingProxy.java index aa35c5bdaed..12d4b4ceb02 100644 --- a/jdk/src/share/classes/com/sun/jmx/namespace/RoutingProxy.java +++ b/jdk/src/share/classes/com/sun/jmx/namespace/RoutingProxy.java @@ -30,6 +30,7 @@ import java.io.IOException; import java.util.logging.Level; import java.util.logging.Logger; +import javax.management.InstanceNotFoundException; import javax.management.MBeanException; import javax.management.MBeanRegistrationException; @@ -90,17 +91,9 @@ import javax.management.namespace.JMXNamespaces; // targetNs= // context must be removed from object name // sourceNs="" // nothing to add... // -// RoutingProxies can also be used on the client side to implement -// "withClientContext" operations. In that case, the boolean parameter -// 'forwards context' is set to true, targetNs is "", and sourceNS may -// also be "". When forwardsContext is true, the RoutingProxy dynamically -// creates an ObjectNameRouter for each operation - in order to dynamically add -// the context attached to the thread to the routing ObjectName. This is -// performed in the getObjectNameRouter() method. -// // Finally, in order to avoid too many layers of wrapping, // RoutingConnectionProxy and RoutingServerProxy can be created through a -// factory method that can concatenate namespace pathes in order to +// factory method that can concatenate namespace paths in order to // return a single RoutingProxy - rather than wrapping a RoutingProxy inside // another RoutingProxy. See RoutingConnectionProxy.cd and // RoutingServerProxy.cd @@ -146,25 +139,27 @@ public abstract class RoutingProxy private final T source; // The name space we're narrowing to (usually some name space in - // the source MBeanServerConnection + // the source MBeanServerConnection), e.g. "a" for the namespace + // "a//". This is empty in the case of ClientContext described above. private final String sourceNs; - // The name space we pretend to be mounted in (usually "") + // The name space we pretend to be mounted in. This is empty except + // in the case of ClientContext described above (where it will be + // something like "jmx.context//foo=bar". private final String targetNs; // The name of the JMXNamespace that handles the source name space private final ObjectName handlerName; private final ObjectNameRouter router; - final boolean forwardsContext; private volatile String defaultDomain = null; /** * Creates a new instance of RoutingProxy */ protected RoutingProxy(T source, - String sourceNs, - String targetNs, - boolean forwardsContext) { + String sourceNs, + String targetNs, + boolean probe) { if (source == null) throw new IllegalArgumentException("null"); this.sourceNs = JMXNamespaces.normalizeNamespaceName(sourceNs); @@ -177,13 +172,17 @@ public abstract class RoutingProxy // System.err.println("sourceNs: "+sourceNs); this.handlerName = JMXNamespaces.getNamespaceObjectName(this.sourceNs); - try { - // System.err.println("handlerName: "+handlerName); - if (!source.isRegistered(handlerName)) - throw new IllegalArgumentException(sourceNs + - ": no such name space"); - } catch (IOException x) { - throw new IllegalArgumentException("source stale: "+x,x); + if (probe) { + try { + if (!source.isRegistered(handlerName)) { + InstanceNotFoundException infe = + new InstanceNotFoundException(handlerName); + throw new IllegalArgumentException(sourceNs + + ": no such name space", infe); + } + } catch (IOException x) { + throw new IllegalArgumentException("source stale: "+x,x); + } } } this.source = source; @@ -191,7 +190,6 @@ public abstract class RoutingProxy JMXNamespaces.normalizeNamespaceName(targetNs)); this.router = new ObjectNameRouter(this.targetNs,this.sourceNs); - this.forwardsContext = forwardsContext; if (LOG.isLoggable(Level.FINER)) LOG.finer("RoutingProxy for " + this.sourceNs + " created"); @@ -200,14 +198,6 @@ public abstract class RoutingProxy @Override public T source() { return source; } - ObjectNameRouter getObjectNameRouter() { -// TODO: uncomment this when contexts are added -// if (forwardsContext) -// return ObjectNameRouter.wrapWithContext(router); -// else - return router; - } - @Override public ObjectName toSource(ObjectName targetName) throws MalformedObjectNameException { @@ -222,8 +212,7 @@ public abstract class RoutingProxy if (defaultDomain != null) targetName = targetName.withDomain(defaultDomain); } - final ObjectNameRouter r = getObjectNameRouter(); - return r.toSourceContext(targetName,true); + return router.toSourceContext(targetName,true); } @Override @@ -243,8 +232,7 @@ public abstract class RoutingProxy public ObjectName toTarget(ObjectName sourceName) throws MalformedObjectNameException { if (sourceName == null) return null; - final ObjectNameRouter r = getObjectNameRouter(); - return r.toTargetContext(sourceName,false); + return router.toTargetContext(sourceName,false); } private Object getAttributeFromHandler(String attributeName) @@ -357,11 +345,8 @@ public abstract class RoutingProxy // instance. static interface RoutingProxyFactory> { - R newInstance(T source, - String sourcePath, String targetPath, - boolean forwardsContext); - R newInstance(T source, - String sourcePath); + public R newInstance( + T source, String sourcePath, String targetPath, boolean probe); } // Performs a narrowDownToNamespace operation. @@ -377,7 +362,7 @@ public abstract class RoutingProxy static > R cd(Class routingProxyClass, RoutingProxyFactory factory, - T source, String sourcePath) { + T source, String sourcePath, boolean probe) { if (source == null) throw new IllegalArgumentException("null"); if (source.getClass().equals(routingProxyClass)) { // cast is OK here, but findbugs complains unless we use class.cast @@ -400,14 +385,13 @@ public abstract class RoutingProxy 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); } } diff --git a/jdk/src/share/classes/com/sun/jmx/namespace/RoutingServerProxy.java b/jdk/src/share/classes/com/sun/jmx/namespace/RoutingServerProxy.java index f58e39816d3..a11b0eccbf5 100644 --- a/jdk/src/share/classes/com/sun/jmx/namespace/RoutingServerProxy.java +++ b/jdk/src/share/classes/com/sun/jmx/namespace/RoutingServerProxy.java @@ -54,7 +54,6 @@ import javax.management.OperationsException; import javax.management.QueryExp; import javax.management.ReflectionException; import javax.management.loading.ClassLoaderRepository; -import javax.management.namespace.JMXNamespaces; /** * A RoutingServerProxy is an MBeanServer proxy that proxies a @@ -76,19 +75,11 @@ public class RoutingServerProxy extends RoutingProxy implements MBeanServer { - /** - * Creates a new instance of RoutingServerProxy - */ - public RoutingServerProxy(MBeanServer source, - String sourceNs) { - this(source,sourceNs,"",false); - } - public RoutingServerProxy(MBeanServer source, String sourceNs, String targetNs, - boolean forwardsContext) { - super(source,sourceNs,targetNs,forwardsContext); + boolean probe) { + super(source, sourceNs, targetNs, probe); } /** @@ -571,20 +562,15 @@ public class RoutingServerProxy FACTORY = new RoutingProxyFactory() { 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); } } diff --git a/jdk/src/share/classes/com/sun/jmx/remote/util/EventClientConnection.java b/jdk/src/share/classes/com/sun/jmx/remote/util/EventClientConnection.java index b660898731a..d02da69c047 100644 --- a/jdk/src/share/classes/com/sun/jmx/remote/util/EventClientConnection.java +++ b/jdk/src/share/classes/com/sun/jmx/remote/util/EventClientConnection.java @@ -430,13 +430,11 @@ public class EventClientConnection implements InvocationHandler, * The {@code EventClient} is created lazily, when it is needed * for the first time. If null, a default factory will be used * (see {@link #createEventClient}). - * @return the + * @return the MBeanServerConnection. **/ public static MBeanServerConnection getEventConnectionFor( MBeanServerConnection connection, Callable 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"); diff --git a/jdk/src/share/classes/javax/management/ClientContext.java b/jdk/src/share/classes/javax/management/ClientContext.java new file mode 100644 index 00000000000..bddde7ca64c --- /dev/null +++ b/jdk/src/share/classes/javax/management/ClientContext.java @@ -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; + +/** + *

Methods to communicate a client context to MBeans. A context is + * a {@literal Map} 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.

+ * + *

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.

+ * + *

The locale case is sufficiently important that it has a special + * shorthand, the {@link #getLocale()} method. This method calls + * {@link #getContext()}.get({@link #LOCALE_KEY}) and converts the + * resultant String into a Locale object.

+ * + *

Here is what an MBean with a localized {@code LastProblemDescription} + * attribute might look like:

+ * + *
+ * 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);
+ *     }
+ *     ...
+ * }
+ * 
+ * + *

Here is how a client can communicate its locale to the target + * MBean:

+ * + *
+ * JMXConnector connector = JMXConnectorFactory.connect(url);
+ * MBeanServerConnection connection = connector.getMBeanServerConnection();
+ * MBeanServerConnection localizedConnection =
+ *     {@link #withLocale(MBeanServerConnection, Locale)
+ *      ClientContext.withLocale}(connection, Locale.getDefault());
+ * String problem = localizedConnection.getAttribute(
+ *          objectName, "LastProblemDescription");
+ * 
+ * + *

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()}.

+ * + * + *

Remote use of contexts

+ * + *

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

+ * + *
+ * MBeanServerConnection mbsc = ...;
+ * Locale french = new Locale("fr");
+ * MBeanServerConnection localizedConnection = ClientContext.withLocale(mbsc, french);
+ * 
+ * + *

...or, equivalently, like this...

+ * + *
+ * MBeanServerConnection localizedConnection =
+ *     ClientContext.withContext(mbsc, {@link #LOCALE_KEY "jmx.locale"}, "fr");
+ * 
+ * + *

...then the context associates {@code "jmx.locale"} with {@code "fr"} + * and a request such as
+ * {@code localizedConnection.getAttribute("java.lang:type=Runtime", "Name")}
+ * is translated into
+ * {@code mbsc.getAttribute("jmx.context//jmx.locale=fr//java.lang:Runtime", "Name")}.
+ * 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
+ * {@code getAttribute("java.lang:Runtime", "Name")}.

+ * + *

The details of how contexts are encoded into ObjectNames are explained + * in the {@link #encode encode} method.

+ * + *

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 + * simulated 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}.

+ */ +public class ClientContext { + /** + *

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()}.

+ */ + public static final String LOCALE_KEY = "jmx.locale"; + + private static final Logger LOG = + Logger.getLogger("javax.management.context"); + + /** + *

The namespace that implements contexts, {@value}.

+ */ + 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> contextThreadLocal = + new InheritableThreadLocal>() { + @Override + protected Map initialValue() { + return Collections.emptyMap(); + } + }; + + /** There are no instances of this class. */ + private ClientContext() { + } + + /** + *

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 getContext() { + return Collections.unmodifiableMap(contextThreadLocal.get()); + } + + /** + *

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); + } + + /** + *

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

+ * + *

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.

+ * + *

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

+ * + *

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:

+ * + *
+     * {@code Map} context =
+     *     new {@code HashMap}(ClientContext.getContext());
+     * context.put(ClientContext.LOCALE_KEY, "fr");
+     * String lastProblemDescription =
+     *     ClientContext.doWithContext(context, new {@code Callable}() {
+     *         public String call() {
+     *             return (String) mbeanServer.getAttribute(mbean, "LastProblemDescription");
+     *         }
+     *     });
+     * 
+ * + * @param 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} + * then {@code T} is {@code String}. If the task does not return a value, + * use a {@code Callable} 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 doWithContext(Map context, Callable task) + throws Exception { + if (context == null || task == null) + throw new IllegalArgumentException("Null parameter"); + Map contextCopy = new TreeMap(context); + validateContext(contextCopy); + Map oldContextMap = contextThreadLocal.get(); + try { + contextThreadLocal.set(contextCopy); + return task.call(); + } finally { + contextThreadLocal.set(oldContextMap); + } + } + + private static void validateContext(Map context) { + for (Map.Entry entry : context.entrySet()) { + // If the user passes a raw Map rather than a Map, + // 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"); + } + } + + /** + *

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

+ * + *

This method is equivalent to {@link #withContext(MBeanServer, + * String, String) withContext}(mbs, {@value LOCALE_KEY}, + * locale.toString()).

+ * + * @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); + } + + /** + *

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

+ * + *

This method is equivalent to {@link #withContext(MBeanServerConnection, + * String, String) withContext}(mbs, {@value LOCALE_KEY}, + * locale.toString()).

+ * + * @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 withLocale( + T mbsc, Class mbscClass, Locale locale) { + if (locale == null) + throw new IllegalArgumentException("Null locale"); + return withContext(mbsc, mbscClass, LOCALE_KEY, locale.toString()); + } + + /** + *

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

+ * + * @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); + } + + /** + *

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

+ * + * @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); + } + + + /** + *

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

+ * + *

For example, suppose the remote system does support contexts, and you + * have created a {@code JMXConnector} like this:

+ * + *
+     * JMXServiceURL url = ...;
+     * JMXConnector client = JMXConnectorFactory.connect(url);
+     * MBeanServerConnection mbsc = client.getMBeanServerConnection();
+     * mbsc = ClientContext.withDynamicContext(mbsc);
+     * 
+ * + *

Then if you do this...

+ * + *
+     * MBeanInfo mbi = ClientContext.doWithContext(
+     *     Collections.singletonMap(ClientContext.LOCALE_KEY, "fr"),
+     *     new {@code Callable}() {
+     *         public MBeanInfo call() {
+     *             return mbsc.getMBeanInfo(objectName);
+     *         }
+     *     });
+     * 
+ * + *

...then the context with the locale set to "fr" will be in place + * when the {@code getMBeanInfo} is executed on the remote MBean Server.

+ * + * @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 withContext( + T mbsc, Class mbscClass, String key, String value) { + return withContext( + mbsc, mbscClass, Collections.singletonMap(key, value), true); + } + + private static T withContext( + T mbsc, Class mbscClass, Map context, + boolean probe) { + if (mbsc == null || context == null) + throw new IllegalArgumentException("Null parameter"); + if (context.isEmpty()) + return mbsc; + validateContext(context); + Map 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); + } + + /** + *

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"}.

+ * + *

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.

+ * + * @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 context) { + if (context == null) + throw new IllegalArgumentException("Null context"); + if (context.isEmpty()) + return ""; + StringBuilder sb = new StringBuilder(); + for (Map.Entry 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(); + } + + /** + *

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.

+ * + *

This forwarder can be used in a number of ways:

+ * + *
    + *
  • + *

    To add context decoding to a local {@code MBeanServer}, you can + * write:

    + *
    +     * MBeanServer mbs = {@link
    +     * java.lang.management.ManagementFactory#getPlatformMBeanServer()
    +     * ManagementFactory.getPlatformMBeanServer()};  // for example
    +     * mbs = ClientContext.newContextForwarder(mbs, null);
    +     * 
    + * + *
  • + *

    To add context decoding to a {@linkplain + * javax.management.remote.JMXConnectorServer connector server}:

    + *
    +     * JMXConnectorServer cs = JMXConnectorServerFactory.newJMXConnectorServer(...);
    +     * MBeanServer nextMBS = cs.getMBeanServer();
    +     * MBeanServerForwarder mbsf = ClientContext.newContextForwarder(nextMBS, null);
    +     * cs.{@link
    +     * javax.management.remote.JMXConnectorServer#setMBeanServerForwarder
    +     * setMBeanServerForwarder}(mbsf);
    +     * 
    + * + *
  • + *

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

    + * + *
+ * + * @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; + } + + /** + *

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)}.

+ * + *

This forwarder can be used in a number of ways:

+ * + *
    + *
  • + *

    To add description localization to a local {@code MBeanServer}, you + * can write:

    + * + *
    +     * MBeanServer mbs = {@link
    +     * java.lang.management.ManagementFactory#getPlatformMBeanServer()
    +     * ManagementFactory.getPlatformMBeanServer()};  // for example
    +     * mbs = ClientContext.newLocalizeMBeanInfoForwarder(mbs);
    +     * 
    + * + *
  • + *

    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:

    + * + *
    +     * 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);
    +     * 
    + * + *

    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.

    + * + *
  • + *

    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:

    + *

    + *
    +     * MBeanServer mbs = ...;
    +     * JMXServiceURL url = new JMXServiceURL("service:jmx:rmi://...");
    +     * {@code Map} env = new {@code HashMap}();
    +     * env.put(JMXConnectorServer.LOCALIZE_MBEAN_INFO_FORWARDER, "true");
    +     * JMXConnectorServer cs = JMXConnectorServerFactory.newJMXConnectorServer(
    +     *     url, env, mbs);
    +     * 
    + * + *
+ * + * @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 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 ctx = new TreeMap(getContext()); + ctx.putAll(stringToMap(encodedContext)); + + return doWithContext(ctx, new Callable() { + 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 map) { + StringBuilder sb = new StringBuilder(); + for (Map.Entry 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 stringToMap(String encodedContext) { + Map map = stringToMapOrNull(encodedContext); + if (map == null) { + throw new IllegalArgumentException( + "Invalid encoded context: " + encodedContext); + } + return map; + } + + private static Map stringToMapOrNull(String encodedContext) { + Map map = new LinkedHashMap(); + 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); + } + } +} diff --git a/jdk/src/share/classes/javax/management/Descriptor.java b/jdk/src/share/classes/javax/management/Descriptor.java index a74fb11cde7..b9c98f4068b 100644 --- a/jdk/src/share/classes/javax/management/Descriptor.java +++ b/jdk/src/share/classes/javax/management/Descriptor.java @@ -35,8 +35,8 @@ import java.io.Serializable; // Javadoc imports: import java.lang.management.MemoryUsage; import java.util.Arrays; +import java.util.Locale; import java.util.ResourceBundle; - import javax.management.openmbean.CompositeData; import javax.management.openmbean.MXBeanMappingFactory; import javax.management.openmbean.OpenMBeanAttributeInfoSupport; @@ -118,19 +118,22 @@ import javax.management.openmbean.OpenType; * deprecation, for example {@code "1.3 Replaced by the Capacity * attribute"}. * - * - * descriptionResource
BundleBaseNameStringAny + * descriptionResource
+ * BundleBaseName
StringAny * * 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"}. + * {@code "com.example.myapp.MBeanResources"}. See + * {@link MBeanInfo#localizeDescriptions MBeanInfo.localizeDescriptions}. * - * - * descriptionResourceKeyStringAny + * descriptionResourceKey + * StringAny * * 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. + * this can be used to find a localized version of the description. + * See {@link MBeanInfo#localizeDescriptions MBeanInfo.localizeDescriptions}. + * * * enabledString * MBeanAttributeInfo
MBeanNotificationInfo
MBeanOperationInfo @@ -157,11 +160,11 @@ import javax.management.openmbean.OpenType; * href="MBeanInfo.html#info-changed">{@code "jmx.mbean.info.changed"} * notification. * - * infoTimeoutString
LongMBeanInfo + * infoTimeoutString
LongMBeanInfo * - * 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 + * The time in milli-seconds that the MBeanInfo can reasonably be + * expected to be unchanged. The value can be a {@code Long} or a + * decimal string. This provides a hint from a DynamicMBean or any * MBean that does not define {@code immutableInfo} as {@code true} * that the MBeanInfo is not likely to change within this period and * therefore can be cached. When this field is missing or has the @@ -185,6 +188,13 @@ import javax.management.openmbean.OpenType; * Legal values for an attribute or parameter. See * {@link javax.management.openmbean}. * + * locale + * StringAny + * + * The {@linkplain Locale locale} of the description in this + * {@code MBeanInfo}, {@code MBeanAttributeInfo}, etc, as returned + * by {@link Locale#toString()}. + * * maxValueObject * MBeanAttributeInfo
MBeanParameterInfo * diff --git a/jdk/src/share/classes/javax/management/JMX.java b/jdk/src/share/classes/javax/management/JMX.java index ac7468d07dd..90770d48110 100644 --- a/jdk/src/share/classes/javax/management/JMX.java +++ b/jdk/src/share/classes/javax/management/JMX.java @@ -30,6 +30,7 @@ import com.sun.jmx.mbeanserver.MBeanInjector; import com.sun.jmx.remote.util.ClassLogger; import java.beans.BeanInfo; import java.beans.PropertyDescriptor; +import java.io.IOException; import java.io.Serializable; import java.lang.reflect.InvocationHandler; import java.lang.reflect.InvocationTargetException; @@ -37,6 +38,7 @@ import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.Map; import java.util.TreeMap; +import javax.management.namespace.JMXNamespaces; import javax.management.openmbean.MXBeanMappingFactory; /** @@ -60,6 +62,21 @@ public class JMX { */ public static final String DEFAULT_VALUE_FIELD = "defaultValue"; + /** + * The name of the {@code + * descriptionResourceBundleBaseName} field. + */ + public static final String DESCRIPTION_RESOURCE_BUNDLE_BASE_NAME_FIELD = + "descriptionResourceBundleBaseName"; + + /** + * The name of the {@code + * descriptionResourceKey} field. + */ + public static final String DESCRIPTION_RESOURCE_KEY_FIELD = + "descriptionResourceKey"; + /** * The name of the {@code * immutableInfo} field. @@ -78,6 +95,12 @@ public class JMX { */ public static final String LEGAL_VALUES_FIELD = "legalValues"; + /** + * The name of the {@code locale} + * field. + */ + public static final String LOCALE_FIELD = "locale"; + /** * The name of the {@code * maxValue} field. @@ -120,13 +143,12 @@ public class JMX { *

Options to apply to an MBean proxy or to an instance of {@link * StandardMBean}.

* - *

For example, to specify a custom {@link MXBeanMappingFactory} - * for a {@code StandardMBean}, you might write this:

+ *

For example, to specify the "wrapped object visible" option for a + * {@code StandardMBean}, you might write this:

* *
-     * 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);
      * 
* diff --git a/jdk/src/share/classes/javax/management/MBeanInfo.java b/jdk/src/share/classes/javax/management/MBeanInfo.java index 3561e3cfc13..49973c66ce6 100644 --- a/jdk/src/share/classes/javax/management/MBeanInfo.java +++ b/jdk/src/share/classes/javax/management/MBeanInfo.java @@ -25,6 +25,7 @@ package javax.management; +import com.sun.jmx.mbeanserver.Util; import java.io.IOException; import java.io.StreamCorruptedException; import java.io.Serializable; @@ -37,6 +38,12 @@ import java.util.WeakHashMap; import java.security.AccessController; import java.security.PrivilegedAction; +import java.util.HashMap; +import java.util.Locale; +import java.util.MissingResourceException; +import java.util.ResourceBundle; +import java.util.logging.Level; +import java.util.logging.Logger; import static javax.management.ImmutableDescriptor.nonNullDescriptor; /** @@ -290,6 +297,7 @@ public class MBeanInfo implements Cloneable, Serializable, DescriptorRead { *

Since this class is immutable, the clone method is chiefly of * interest to subclasses.

*/ + @Override public Object clone () { try { return super.clone() ; @@ -474,6 +482,7 @@ public class MBeanInfo implements Cloneable, Serializable, DescriptorRead { return (Descriptor) nonNullDescriptor(descriptor).clone(); } + @Override public String toString() { return getClass().getName() + "[" + @@ -505,6 +514,7 @@ public class MBeanInfo implements Cloneable, Serializable, DescriptorRead { * @return true if and only if o is an MBeanInfo that is equal * to this one according to the rules above. */ + @Override public boolean equals(Object o) { if (o == this) return true; @@ -524,6 +534,7 @@ public class MBeanInfo implements Cloneable, Serializable, DescriptorRead { Arrays.equals(p.fastGetNotifications(), fastGetNotifications())); } + @Override public int hashCode() { /* Since computing the hashCode is quite expensive, we cache it. If by some terrible misfortune the computed value is 0, the @@ -747,4 +758,377 @@ public class MBeanInfo implements Cloneable, Serializable, DescriptorRead { throw new StreamCorruptedException("Got unexpected byte."); } } + + /** + *

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

+ * + *

Here is how the description {@code this.getDescription()} is + * localized.

+ * + *

First, if the {@linkplain #getDescriptor() descriptor} + * of this {@code MBeanInfo} contains a field "locale", 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()}. + * + *

A {@code className} is determined. If this + * {@code MBeanInfo} contains a descriptor with the field + * {@code + * "interfaceClassName"}, 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 {@code package}, and everything after is the {@code + * simpleClassName}. (If there is no period, then the {@code package} + * is empty and the {@code simpleClassName} is the same as the {@code + * className}.)

+ * + *

A {@code resourceKey} 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"}.

+ * + *

A {@code resourceBundleBaseName} 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"}. + * + *

Then, a {@link java.util.ResourceBundle ResourceBundle} is + * determined, using
{@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.

+ * + *

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"}.

+ * + *

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"}.

+ * + *

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.

+ * + *

Example

+ * + *

Suppose you have an MBean defined by these two Java source files:

+ * + *
+     * // 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) {
+     *         ...
+     *     }
+     *     ...
+     * }
+     * 
+ * + *

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.

+ * + *

The file {@code com/example/MBeanDescriptions.properties} might + * look like this:

+ * + *
+     * # 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
+     * 
+ * + *

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: + * + *

+     * 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éfaut
+     * ConfigurationMBean.constructor.com.example.Configuration.p1 = Nom du fichier par défaut
+     * 
+ * + *

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 @Description} annotations. Annotations + * provide descriptions by default but are overridden if {@code + * localizeDescriptions} is called.

+ * + * @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 map = new HashMap(); + 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[] 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 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.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"); + } + } } diff --git a/jdk/src/share/classes/javax/management/MBeanServerNotification.java b/jdk/src/share/classes/javax/management/MBeanServerNotification.java index 723d2d4c1e6..55e71cec331 100644 --- a/jdk/src/share/classes/javax/management/MBeanServerNotification.java +++ b/jdk/src/share/classes/javax/management/MBeanServerNotification.java @@ -27,15 +27,43 @@ package javax.management; /** - * Represents a notification emitted by the MBean server through the MBeanServerDelegate MBean. + * Represents a notification emitted by the MBean Server through the MBeanServerDelegate MBean. * The MBean Server emits the following types of notifications: MBean registration, MBean - * de-registration. + * unregistration. *

- * 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 * JMImplementation:type=MBeanServerDelegate. * + *

The following code prints a message every time an MBean is registered + * or unregistered in the MBean Server {@code mbeanServer}:

+ * + *
+ * 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);
+ * 
+ * * @since 1.5 */ public class MBeanServerNotification extends Notification { diff --git a/jdk/src/share/classes/javax/management/Notification.java b/jdk/src/share/classes/javax/management/Notification.java index 07fc19b3e36..de07ec44748 100644 --- a/jdk/src/share/classes/javax/management/Notification.java +++ b/jdk/src/share/classes/javax/management/Notification.java @@ -54,7 +54,7 @@ import com.sun.jmx.mbeanserver.GetPropertyAction; * @since 1.5 */ @SuppressWarnings("serial") // serialVersionUID is not constant -public class Notification extends EventObject { +public class Notification extends EventObject implements Cloneable { // Serialization compatibility stuff: // Two serial forms are supported in this class. The selected form depends @@ -243,6 +243,26 @@ public class Notification extends EventObject { this.message = message ; } + /** + *

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.

+ * + * @return a copy of this object. + */ + @Override + public Object clone() { + try { + return super.clone(); + } catch (CloneNotSupportedException e) { + throw new AssertionError(e); + } + } + /** * Sets the source. * @@ -285,8 +305,10 @@ public class Notification extends EventObject { /** * Get the notification type. * - * @return The notification type. It's a string expressed in a dot notation similar - * to Java properties. An example of a notification type is network.alarm.router . + * @return The notification type. It's a string expressed in a dot notation + * similar to Java properties. It is recommended that the notification type + * should follow the reverse-domain-name convention used by Java package + * names. An example of a notification type is com.example.alarm.router. */ public String getType() { return type ; @@ -317,14 +339,25 @@ public class Notification extends EventObject { /** * Get the notification message. * - * @return The message string of this notification object. It contains in a string, - * which could be the explanation of the notification for displaying to a user + * @return The message string of this notification object. * + * @see #setMessage */ public String getMessage() { return message ; } + /** + * Set the notification message. + * + * @param message the new notification message. + * + * @see #getMessage + */ + public void setMessage(String message) { + this.message = message; + } + /** * Get the user data. * @@ -355,6 +388,7 @@ public class Notification extends EventObject { * * @return A String representation of this notification. */ + @Override public String toString() { return super.toString()+"[type="+type+"][message="+message+"]"; } diff --git a/jdk/src/share/classes/javax/management/event/EventClient.java b/jdk/src/share/classes/javax/management/event/EventClient.java index 96c4017e450..a2f6bc617fe 100644 --- a/jdk/src/share/classes/javax/management/event/EventClient.java +++ b/jdk/src/share/classes/javax/management/event/EventClient.java @@ -29,7 +29,6 @@ import com.sun.jmx.event.DaemonThreadFactory; import com.sun.jmx.event.LeaseRenewer; import com.sun.jmx.event.ReceiverBuffer; import com.sun.jmx.event.RepeatedSingletonJob; -import com.sun.jmx.namespace.JMXNamespaceUtils; import com.sun.jmx.mbeanserver.PerThreadGroupPool; import com.sun.jmx.remote.util.ClassLogger; @@ -58,7 +57,6 @@ import javax.management.NotificationBroadcasterSupport; import javax.management.NotificationFilter; import javax.management.NotificationListener; import javax.management.ObjectName; -import javax.management.remote.JMXConnector; import javax.management.remote.NotificationResult; import javax.management.remote.TargetedNotification; @@ -129,11 +127,12 @@ public class EventClient implements EventConsumer, NotificationManager { public static final String NOTIFS_LOST = "jmx.event.service.notifs.lost"; /** - * The default lease time, {@value}, in milliseconds. + * The default lease time that EventClient instances will request, in + * milliseconds. This value is {@value}. * * @see EventClientDelegateMBean#lease */ - public static final long DEFAULT_LEASE_TIMEOUT = 300000; + public static final long DEFAULT_REQUESTED_LEASE_TIME = 300000; /** *

Constructs a default {@code EventClient} object.

@@ -173,7 +172,7 @@ public class EventClient implements EventConsumer, NotificationManager { */ public EventClient(EventClientDelegateMBean delegate) throws IOException { - this(delegate, null, null, null, DEFAULT_LEASE_TIMEOUT); + this(delegate, null, null, null, DEFAULT_REQUESTED_LEASE_TIME); } /** @@ -196,7 +195,7 @@ public class EventClient implements EventConsumer, NotificationManager { * If {@code null}, a default scheduler will be used. * @param requestedLeaseTime The lease time used to keep this client alive * in the {@link EventClientDelegateMBean}. A value of zero is equivalent - * to the {@linkplain #DEFAULT_LEASE_TIMEOUT default value}. + * to the {@linkplain #DEFAULT_REQUESTED_LEASE_TIME default value}. * * @throws IllegalArgumentException If {@code delegate} is null. * @throws IOException If an I/O error occurs when communicating with the @@ -213,7 +212,7 @@ public class EventClient implements EventConsumer, NotificationManager { } if (requestedLeaseTime == 0) - requestedLeaseTime = DEFAULT_LEASE_TIMEOUT; + requestedLeaseTime = DEFAULT_REQUESTED_LEASE_TIME; else if (requestedLeaseTime < 0) { throw new IllegalArgumentException( "Negative lease time: " + requestedLeaseTime); @@ -269,7 +268,13 @@ public class EventClient implements EventConsumer, NotificationManager { new ScheduledThreadPoolExecutor(20, daemonThreadFactory); executor.setKeepAliveTime(1, TimeUnit.SECONDS); executor.allowCoreThreadTimeOut(true); - executor.setRemoveOnCancelPolicy(true); + if (setRemoveOnCancelPolicy != null) { + try { + setRemoveOnCancelPolicy.invoke(executor, true); + } catch (Exception e) { + logger.trace("setRemoveOnCancelPolicy", e); + } + } // By default, a ScheduledThreadPoolExecutor will keep jobs // in its queue even after they have been cancelled. They // will only be removed when their scheduled time arrives. @@ -277,12 +282,25 @@ public class EventClient implements EventConsumer, NotificationManager { // this EventClient, this can lead to a moderately large number // of objects remaining referenced until the renewal time // arrives. Hence the above call, which removes the job from - // the queue as soon as it is cancelled. + // the queue as soon as it is cancelled. Since the call is + // new with JDK 7, we invoke it via reflection to make it + // easier to use this code on JDK 6. return executor; } }; return leaseRenewerThreadPool.getThreadPoolExecutor(create); + } + private static final Method setRemoveOnCancelPolicy; + static { + Method m; + try { + m = ScheduledThreadPoolExecutor.class.getMethod( + "setRemoveOnCancelPolicy", boolean.class); + } catch (Exception e) { + m = null; + } + setRemoveOnCancelPolicy = m; } /** @@ -1042,7 +1060,7 @@ public class EventClient implements EventConsumer, NotificationManager { final public EventClient call() throws Exception { EventClientDelegateMBean ecd = EventClientDelegate.getProxy(conn); return new EventClient(ecd, eventRelay, null, null, - DEFAULT_LEASE_TIMEOUT); + DEFAULT_REQUESTED_LEASE_TIME); } }; @@ -1080,24 +1098,6 @@ public class EventClient implements EventConsumer, NotificationManager { return clientId; } - /** - * Returns a JMX Connector that will use an {@link EventClient} - * to subscribe for notifications. If the server doesn't have - * an {@link EventClientDelegateMBean}, then the connector will - * use the legacy notification mechanism instead. - * - * @param wrapped The underlying JMX Connector wrapped by the returned - * connector. - * - * @return A JMX Connector that will uses an {@link EventClient}, if - * available. - * - * @see EventClient#getEventClientConnection(MBeanServerConnection) - */ - public static JMXConnector withEventClient(final JMXConnector wrapped) { - return JMXNamespaceUtils.withEventClient(wrapped); - } - private static final PerThreadGroupPool leaseRenewerThreadPool = PerThreadGroupPool.make(); } diff --git a/jdk/src/share/classes/javax/management/event/EventClientDelegate.java b/jdk/src/share/classes/javax/management/event/EventClientDelegate.java index 13329ea9e6f..8eeeeb1f5c1 100644 --- a/jdk/src/share/classes/javax/management/event/EventClientDelegate.java +++ b/jdk/src/share/classes/javax/management/event/EventClientDelegate.java @@ -149,6 +149,7 @@ public class EventClientDelegate implements EventClientDelegateMBean { // of a setMBeanServer on some other forwarder later in the chain. private static class Forwarder extends SingleMBeanForwarder { + private MBeanServer loopMBS; private static class UnsupportedInvocationHandler implements InvocationHandler { @@ -173,7 +174,11 @@ public class EventClientDelegate implements EventClientDelegateMBean { private volatile boolean madeECD; Forwarder() { - super(OBJECT_NAME, makeUnsupportedECD()); + super(OBJECT_NAME, makeUnsupportedECD(), true); + } + + synchronized void setLoopMBS(MBeanServer loopMBS) { + this.loopMBS = loopMBS; } @Override @@ -186,7 +191,7 @@ public class EventClientDelegate implements EventClientDelegateMBean { AccessController.doPrivileged( new PrivilegedAction() { public EventClientDelegate run() { - return getEventClientDelegate(Forwarder.this); + return getEventClientDelegate(loopMBS); } }); DynamicMBean mbean = new StandardMBean( @@ -208,11 +213,46 @@ public class EventClientDelegate implements EventClientDelegateMBean { * that are targeted for that MBean and handles them itself. All other * requests are forwarded to the next element in the forwarder chain.

* + * @param nextMBS the next {@code MBeanServer} in the chain of forwarders, + * which might be another {@code MBeanServerForwarder} or a plain {@code + * MBeanServer}. This is the object to which {@code MBeanServer} requests + * that do not concern the {@code EventClientDelegateMBean} are sent. + * It will be the value of {@link MBeanServerForwarder#getMBeanServer() + * getMBeanServer()} on the returned object, and can be changed with {@link + * MBeanServerForwarder#setMBeanServer setMBeanServer}. It can be null but + * must be set to a non-null value before any {@code MBeanServer} requests + * arrive. + * + * @param loopMBS the {@code MBeanServer} to which requests from the + * {@code EventClientDelegateMBean} should be sent. For example, + * when you invoke the {@link EventClientDelegateMBean#addListener + * addListener} operation on the {@code EventClientDelegateMBean}, it will + * result in a call to {@link + * MBeanServer#addNotificationListener(ObjectName, NotificationListener, + * NotificationFilter, Object) addNotificationListener} on this object. + * If this parameter is null, then these requests will be sent to the + * newly-created {@code MBeanServerForwarder}. Usually the parameter will + * either be null or will be the result of {@link + * javax.management.remote.JMXConnectorServer#getSystemMBeanServerForwarder() + * getSystemMBeanServerForwarder()} for the connector server in which + * this forwarder will be installed. + * * @return a new {@code MBeanServerForwarder} that simulates the existence * of an {@code EventClientDelegateMBean}. + * + * @see javax.management.remote.JMXConnectorServer#installStandardForwarders */ - public static MBeanServerForwarder newForwarder() { - return new Forwarder(); + public static MBeanServerForwarder newForwarder( + MBeanServer nextMBS, MBeanServer loopMBS) { + Forwarder mbsf = new Forwarder(); + // We must setLoopMBS before setMBeanServer, because when we + // setMBeanServer that will call getEventClientDelegate(loopMBS). + if (loopMBS == null) + loopMBS = mbsf; + mbsf.setLoopMBS(loopMBS); + if (nextMBS != null) + mbsf.setMBeanServer(nextMBS); + return mbsf; } /** @@ -437,10 +477,9 @@ public class EventClientDelegate implements EventClientDelegateMBean { // private classes // ------------------------------------ private class ClientInfo { - String clientId; - EventBuffer buffer; - NotificationListener clientListener; - Map listenerInfoMap = + final String clientId; + final NotificationListener clientListener; + final Map listenerInfoMap = new HashMap(); ClientInfo(String clientId, EventForwarder forwarder) { @@ -703,7 +742,8 @@ public class EventClientDelegate implements EventClientDelegateMBean { clientInfo = clientInfoMap.get(clientId); if (clientInfo == null) { - throw new EventClientNotFoundException("The client is not found."); + throw new EventClientNotFoundException( + "Client not found (id " + clientId + ")"); } return clientInfo; diff --git a/jdk/src/share/classes/javax/management/event/EventClientDelegateMBean.java b/jdk/src/share/classes/javax/management/event/EventClientDelegateMBean.java index a9718a931dc..e6030b613a9 100644 --- a/jdk/src/share/classes/javax/management/event/EventClientDelegateMBean.java +++ b/jdk/src/share/classes/javax/management/event/EventClientDelegateMBean.java @@ -51,7 +51,8 @@ import javax.management.remote.NotificationResult; * and the MBean Server, that will intercept accesses to the Event Client * Delegate MBean and treat them as the real MBean would. This forwarder is * inserted by default with the standard RMI Connector Server, and can also - * be created explicitly using {@link EventClientDelegate#newForwarder()}. + * be created explicitly using {@link EventClientDelegate#newForwarder + * EventClientDelegate.newForwarder}. * *
  • A variant on the above is to replace the MBean Server that is * used locally with a forwarder as described above. Since @@ -61,9 +62,7 @@ import javax.management.remote.NotificationResult; * *

      * 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
      * 
    * diff --git a/jdk/src/share/classes/javax/management/event/EventRelay.java b/jdk/src/share/classes/javax/management/event/EventRelay.java index d723bb04c4e..d106a160551 100644 --- a/jdk/src/share/classes/javax/management/event/EventRelay.java +++ b/jdk/src/share/classes/javax/management/event/EventRelay.java @@ -27,7 +27,6 @@ package javax.management.event; import java.io.IOException; import java.util.concurrent.Executors; // for javadoc -import java.util.concurrent.ScheduledFuture; /** * This interface is used to specify a way to receive diff --git a/jdk/src/share/classes/javax/management/event/package-info.java b/jdk/src/share/classes/javax/management/event/package-info.java index c4c7dbed63d..728b095139e 100644 --- a/jdk/src/share/classes/javax/management/event/package-info.java +++ b/jdk/src/share/classes/javax/management/event/package-info.java @@ -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.

    * diff --git a/jdk/src/share/classes/javax/management/namespace/JMXNamespaces.java b/jdk/src/share/classes/javax/management/namespace/JMXNamespaces.java index 429a9d466d6..f19dfa570e4 100644 --- a/jdk/src/share/classes/javax/management/namespace/JMXNamespaces.java +++ b/jdk/src/share/classes/javax/management/namespace/JMXNamespaces.java @@ -26,21 +26,19 @@ package javax.management.namespace; import com.sun.jmx.defaults.JmxProperties; -import com.sun.jmx.namespace.JMXNamespaceUtils; import com.sun.jmx.namespace.ObjectNameRouter; import com.sun.jmx.namespace.serial.RewritingProcessor; import com.sun.jmx.namespace.RoutingConnectionProxy; import com.sun.jmx.namespace.RoutingServerProxy; -import java.io.IOException; import java.util.logging.Level; import java.util.logging.Logger; +import javax.management.InstanceNotFoundException; import javax.management.MBeanServer; import javax.management.MBeanServerConnection; import javax.management.MalformedObjectNameException; import javax.management.ObjectName; -import javax.management.remote.JMXConnector; /** * Static constants and utility methods to help work with @@ -68,23 +66,6 @@ public class JMXNamespaces { NAMESPACE_SEPARATOR.length(); - /** - * Returns a connector connected to a sub name space exposed through - * the parent connector. - * @param parent the parent connector. - * @param namespace the {@linkplain javax.management.namespace name space} - * to which the returned connector is - * connected. - * @return A connector connected to a sub name space exposed through - * the parent connector. - **/ - public static JMXConnector narrowToNamespace(final JMXConnector parent, - final String namespace) - throws IOException { - - return JMXNamespaceUtils.cd(parent,namespace,true); - } - /** * Creates a new {@code MBeanServerConnection} proxy on a * {@linkplain javax.management.namespace sub name space} @@ -96,15 +77,18 @@ public class JMXNamespaces { * name space} in which to narrow. * @return A new {@code MBeanServerConnection} proxy that shows the content * of that name space. - * @throws IllegalArgumentException if the name space does not exist, or - * if a proxy for that name space cannot be created. + * @throws IllegalArgumentException if either argument is null, + * or the name space does not exist, or if a proxy for that name space + * cannot be created. The {@linkplain Throwable#getCause() cause} of + * this exception will be an {@link InstanceNotFoundException} if and only + * if the name space is found not to exist. */ public static MBeanServerConnection narrowToNamespace( MBeanServerConnection parent, String namespace) { if (LOG.isLoggable(Level.FINER)) LOG.finer("Making MBeanServerConnection for: " +namespace); - return RoutingConnectionProxy.cd(parent,namespace); + return RoutingConnectionProxy.cd(parent, namespace, true); } /** @@ -120,13 +104,15 @@ public class JMXNamespaces { * of that name space. * @throws IllegalArgumentException if either argument is null, * or the name space does not exist, or if a proxy for that name space - * cannot be created. + * cannot be created. The {@linkplain Throwable#getCause() cause} of + * this exception will be an {@link InstanceNotFoundException} if and only + * if the name space is found not to exist. */ public static MBeanServer narrowToNamespace(MBeanServer parent, String namespace) { if (LOG.isLoggable(Level.FINER)) - LOG.finer("Making NamespaceServerProxy for: " +namespace); - return RoutingServerProxy.cd(parent,namespace); + LOG.finer("Making MBeanServer for: " +namespace); + return RoutingServerProxy.cd(parent, namespace, true); } /** @@ -266,7 +252,7 @@ public class JMXNamespaces { ObjectNameRouter.normalizeNamespacePath(namespace,false, true,false); try { - // We could use Util.newObjectName here - but throwing an + // We could use ObjectName.valueOf here - but throwing an // IllegalArgumentException that contains just the supplied // namespace instead of the whole ObjectName seems preferable. return ObjectName.getInstance(sourcePath+ diff --git a/jdk/src/share/classes/javax/management/namespace/JMXRemoteNamespace.java b/jdk/src/share/classes/javax/management/namespace/JMXRemoteNamespace.java index 6958f57f2d7..1c4d29bae30 100644 --- a/jdk/src/share/classes/javax/management/namespace/JMXRemoteNamespace.java +++ b/jdk/src/share/classes/javax/management/namespace/JMXRemoteNamespace.java @@ -27,10 +27,10 @@ package javax.management.namespace; import com.sun.jmx.defaults.JmxProperties; import com.sun.jmx.mbeanserver.Util; -import com.sun.jmx.namespace.JMXNamespaceUtils; import com.sun.jmx.remote.util.EnvHelp; import java.io.IOException; +import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.concurrent.atomic.AtomicLong; @@ -39,6 +39,7 @@ import java.util.logging.Logger; import javax.management.AttributeChangeNotification; +import javax.management.ClientContext; import javax.management.InstanceNotFoundException; import javax.management.ListenerNotFoundException; import javax.management.MBeanNotificationInfo; @@ -220,17 +221,26 @@ public class JMXRemoteNamespace initParentOnce(this); // URL must not be null. - this.jmxURL = JMXNamespaceUtils.checkNonNull(sourceURL,"url"); + if (sourceURL == null) + throw new IllegalArgumentException("Null URL"); + this.jmxURL = sourceURL; this.broadcaster = new NotificationBroadcasterSupport(connectNotification); // handles options - this.optionsMap = JMXNamespaceUtils.unmodifiableMap(optionsMap); + this.optionsMap = unmodifiableMap(optionsMap); // handles (dis)connection events this.listener = new ConnectionListener(); } + // returns un unmodifiable view of a map. + private static Map unmodifiableMap(Map 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.

    @@ -483,106 +493,171 @@ public class JMXRemoteNamespace } } - JMXConnector connect(JMXServiceURL url, Map env) + private JMXConnector connect(JMXServiceURL url, Map 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. - *

    - * This method first calls {@link JMXConnectorFactory#newJMXConnector - * JMXConnectorFactory.newJMXConnector(jmxURL, env)} to obtain a new - * JMX connector, and returns that. - *

    - *

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

    -     * class JMXRemoteSubNamespace extends JMXRemoteNamespace {
    -     *    private final String subnamespace;
    -     *    JMXRemoteSubNamespace(JMXServiceURL url,
    -     *              Map{@code } env, String subnamespace) {
    -     *        super(url,options);
    -     *        this.subnamespace = subnamespace;
    -     *    }
    -     *    protected JMXConnector newJMXConnector(JMXServiceURL url,
    -     *              Map env) throws IOException {
    -     *        final JMXConnector inner = super.newJMXConnector(url,env);
    -     *        return {@link JMXNamespaces#narrowToNamespace(JMXConnector,String)
    -     *               JMXNamespaces.narrowToNamespace(inner,subnamespace)};
    -     *    }
    -     * }
    -     * 
    - *

    - *

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

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

    - *
    -     * class JMXRemoteEventClientNamespace extends JMXRemoteNamespace {
    -     *    JMXRemoteSubNamespaceConnector(JMXServiceURL url,
    -     *              Map env) {
    -     *        super(url,options);
    -     *    }
    -     *    protected JMXConnector newJMXConnector(JMXServiceURL url,
    -     *              Map env) throws IOException {
    -     *        final JMXConnector inner = super.newJMXConnector(url,env);
    -     *        return {@link EventClient#withEventClient(
    -     *                JMXConnector) EventClient.withEventClient(inner)};
    -     *    }
    -     * }
    -     * 
    - *

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

    + *

    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.

    + * * @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 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); } + /** + *

    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:

    + * + *
    +     * MBeanServerConnection mbsc = {@link JMXConnector#getMBeanServerConnection()
    +     *                               jmxc.getMBeanServerConnection()};
    +     * try {
    +     *     return {@link ClientContext#withDynamicContext
    +     *             ClientContext.withDynamicContext(mbsc)};
    +     * } catch (IllegalArgumentException e) {
    +     *     return mbsc;
    +     * }
    +     * 
    + * + *

    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.

    + * + *

    Example: connecting to a remote namespace

    + * + *

    A subclass that wanted to narrow into a namespace of + * the remote MBeanServer might look like this:

    + * + *
    +     * class JMXRemoteSubNamespace extends JMXRemoteNamespace {
    +     *     private final String subnamespace;
    +     *
    +     *     JMXRemoteSubNamespace(
    +     *             JMXServiceURL url, Map{@code } 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)};
    +     *     }
    +     * }
    +     * 
    + * + *

    Example: using the Event Service for notifications

    + * + *

    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.

    + * + *

    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:

    + * + *
    +     * class JMXRemoteEventClientNamespace extends JMXRemoteNamespace {
    +     *     JMXRemoteEventClientNamespace(JMXServiceURL url, {@code Map} env) {
    +     *         super(url, env);
    +     *     }
    +     *
    +     *     {@code @Override}
    +     *     protected MBeanServerConnection getMBeanServerConnection(JMXConnector jmxc)
    +     *             throws IOException {
    +     *         MBeanServerConnection mbsc = super.getMBeanServerConnection(jmxc);
    +     *         return EventClient.getEventClientConnection(mbsc);
    +     *     }
    +     * }
    +     * 
    + * + *

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

    + * + *

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

    + * + * @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} + * + *

    The sequence of events when this method is called includes, + * effectively, the following code:

    + * + *
    +     * JMXServiceURL url = {@link #getJMXServiceURL getJMXServiceURL}();
    +     * JMXConnector jmxc = {@link #newJMXConnector newJMXConnector}(url, env);
    +     * jmxc.connect();
    +     * MBeanServerConnection mbsc = {@link #getMBeanServerConnection(JMXConnector)
    +     *                               getMBeanServerConnection}(jmxc);
    +     * 
    + * + *

    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.

    + * + *

    Subclasses can customize connection behavior by overriding the + * {@code getJMXServiceURL}, {@code newJMXConnector}, or + * {@code getMBeanServerConnection} methods.

    + */ public void connect() throws IOException { LOG.fine("connecting..."); final Map env = @@ -590,7 +665,7 @@ public class JMXRemoteNamespace try { // XXX: We should probably document this... // This allows to specify a loader name - which will be - // retrieved from the paret MBeanServer. + // retrieved from the parent MBeanServer. defaultClassLoader = EnvHelp.resolveServerClassLoader(env,getMBeanServer()); } catch (InstanceNotFoundException x) { @@ -604,7 +679,7 @@ public class JMXRemoteNamespace final JMXConnector aconn = connect(url,env); final MBeanServerConnection msc; try { - msc = aconn.getMBeanServerConnection(); + msc = getMBeanServerConnection(aconn); aconn.addConnectionNotificationListener(listener,null,aconn); } catch (IOException io) { close(aconn); diff --git a/jdk/src/share/classes/javax/management/remote/JMXConnectorFactory.java b/jdk/src/share/classes/javax/management/remote/JMXConnectorFactory.java index 051d8b34c45..77f700c3215 100644 --- a/jdk/src/share/classes/javax/management/remote/JMXConnectorFactory.java +++ b/jdk/src/share/classes/javax/management/remote/JMXConnectorFactory.java @@ -322,10 +322,12 @@ public class JMXConnectorFactory { JMXConnectorProvider.class; final String protocol = serviceURL.getProtocol(); final String providerClassName = "ClientProvider"; + final JMXServiceURL providerURL = serviceURL; - JMXConnectorProvider provider = - getProvider(serviceURL, envcopy, providerClassName, - targetInterface, loader); + JMXConnectorProvider provider = getProvider(providerURL, envcopy, + providerClassName, + targetInterface, + loader); IOException exception = null; if (provider == null) { @@ -336,7 +338,7 @@ public class JMXConnectorFactory { if (loader != null) { try { JMXConnector connection = - getConnectorAsService(loader, serviceURL, envcopy); + getConnectorAsService(loader, providerURL, envcopy); if (connection != null) return connection; } catch (JMXProviderException e) { @@ -345,8 +347,7 @@ public class JMXConnectorFactory { exception = e; } } - provider = - getProvider(protocol, PROTOCOL_PROVIDER_DEFAULT_PACKAGE, + provider = getProvider(protocol, PROTOCOL_PROVIDER_DEFAULT_PACKAGE, JMXConnectorFactory.class.getClassLoader(), providerClassName, targetInterface); } @@ -448,9 +449,10 @@ public class JMXConnectorFactory { getProviderIterator(JMXConnectorProvider.class, loader); JMXConnector connection; IOException exception = null; - while(providers.hasNext()) { + while (providers.hasNext()) { + JMXConnectorProvider provider = providers.next(); try { - connection = providers.next().newJMXConnector(url, map); + connection = provider.newJMXConnector(url, map); return connection; } catch (JMXProviderException e) { throw e; @@ -553,4 +555,5 @@ public class JMXConnectorFactory { private static String protocol2package(String protocol) { return protocol.replace('+', '.').replace('-', '_'); } + } diff --git a/jdk/src/share/classes/javax/management/remote/JMXConnectorServer.java b/jdk/src/share/classes/javax/management/remote/JMXConnectorServer.java index 88348ff5de3..c9b4721911b 100644 --- a/jdk/src/share/classes/javax/management/remote/JMXConnectorServer.java +++ b/jdk/src/share/classes/javax/management/remote/JMXConnectorServer.java @@ -33,6 +33,7 @@ import java.util.List; import java.util.Map; import java.util.NoSuchElementException; +import javax.management.ClientContext; import javax.management.MBeanInfo; // for javadoc import javax.management.MBeanNotificationInfo; import javax.management.MBeanRegistration; @@ -101,6 +102,56 @@ public abstract class JMXConnectorServer public static final String DELEGATE_TO_EVENT_SERVICE = "jmx.remote.delegate.event.service"; + /** + *

    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.

    + * + *

    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"}.

    + * + * @since 1.7 + */ + public static final String CONTEXT_FORWARDER = + "jmx.remote.context.forwarder"; + + /** + *

    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.

    + * + *

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

    + * + *

    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"}.

    + * + *

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

    + * + * @since 1.7 + */ + public static final String LOCALIZE_MBEAN_INFO_FORWARDER = + "jmx.remote.localize.mbean.info"; + /** *

    Name of the attribute that specifies whether this connector * server simulates the existence of the {@link EventClientDelegate} @@ -155,7 +206,7 @@ public abstract class JMXConnectorServer * to, or null if it is not yet attached to an MBean server. * * @see #setMBeanServerForwarder - * @see #getSystemMBeanServer + * @see #getSystemMBeanServerForwarder */ public synchronized MBeanServer getMBeanServer() { return userMBeanServer; @@ -176,30 +227,36 @@ public abstract class JMXConnectorServer * this method, the first occurrence in the chain of an object that is * {@linkplain Object#equals equal} to {@code mbsf} will have been * removed.

    + * * @param mbsf the forwarder to remove + * * @throws NoSuchElementException if there is no occurrence of {@code mbsf} * in the chain. - * @throws IllegalArgumentException if {@code mbsf} is null. + * @throws IllegalArgumentException if {@code mbsf} is null or is the + * {@linkplain #getSystemMBeanServerForwarder() system forwarder}. + * + * @since 1.7 */ public synchronized void removeMBeanServerForwarder(MBeanServerForwarder mbsf) { if (mbsf == null) throw new IllegalArgumentException("Invalid null argument: mbsf"); + if (systemMBeanServerForwarder.equals(mbsf)) + throw new IllegalArgumentException("Cannot remove system forwarder"); - MBeanServerForwarder prev = null; - MBeanServer curr = systemMBeanServer; - while (curr instanceof MBeanServerForwarder && !mbsf.equals(curr)) { - prev = (MBeanServerForwarder) curr; + MBeanServerForwarder prev = systemMBeanServerForwarder; + MBeanServer curr; + while (true) { curr = prev.getMBeanServer(); + if (mbsf.equals(curr)) + break; + if (curr instanceof MBeanServerForwarder) + prev = (MBeanServerForwarder) curr; + else + throw new NoSuchElementException("MBeanServerForwarder not in chain"); } - if (!(curr instanceof MBeanServerForwarder)) - throw new NoSuchElementException("MBeanServerForwarder not in chain"); - MBeanServerForwarder deleted = (MBeanServerForwarder) curr; - MBeanServer next = deleted.getMBeanServer(); - if (prev != null) - prev.setMBeanServer(next); - if (systemMBeanServer == deleted) - systemMBeanServer = next; - if (userMBeanServer == deleted) + MBeanServer next = mbsf.getMBeanServer(); + prev.setMBeanServer(next); + if (userMBeanServer == mbsf) userMBeanServer = next; } @@ -209,66 +266,63 @@ public abstract class JMXConnectorServer * the systemMBeanServer and userMBeanServer field declarations. */ private void insertUserMBeanServer(MBeanServer mbs) { - MBeanServerForwarder lastSystemMBSF = null; - for (MBeanServer mbsi = systemMBeanServer; - mbsi != userMBeanServer; - mbsi = lastSystemMBSF.getMBeanServer()) { + MBeanServerForwarder lastSystemMBSF = systemMBeanServerForwarder; + while (true) { + MBeanServer mbsi = lastSystemMBSF.getMBeanServer(); + if (mbsi == userMBeanServer) + break; lastSystemMBSF = (MBeanServerForwarder) mbsi; } userMBeanServer = mbs; - if (lastSystemMBSF == null) - systemMBeanServer = mbs; - else - lastSystemMBSF.setMBeanServer(mbs); + lastSystemMBSF.setMBeanServer(mbs); } /** *

    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 system chain - * and then the user chain. 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.

    + * 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 system chain and then + * the user chain. 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.

    * - *

    The system chain 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.

    + *

    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.

    + * + *

    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.

    + * + *

    The system chain 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.

    * *

    The user chain is defined by calling {@link - * #setMBeanServerForwarder} to insert forwarders at the head of the user - * chain.

    - * - *

    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.

    + * #setMBeanServerForwarder setMBeanServerForwarder} to insert forwarders + * at the head of the user chain.

    * *

    This code illustrates how the chains can be traversed:

    * *
          * JMXConnectorServer cs;
          * System.out.println("system chain:");
    -     * MBeanServer mbs = cs.getSystemMBeanServer();
    +     * MBeanServer mbs = cs.getSystemMBeanServerForwarder();
          * while (true) {
          *     if (mbs == cs.getMBeanServer())
          *         System.out.println("user chain:");
    @@ -281,65 +335,40 @@ public abstract class JMXConnectorServer
          * System.out.println("--MBean Server");
          * 
    * + *

    Note for connector server implementors

    + * + *

    Existing connector server implementations can be updated to support + * a system chain of forwarders as follows:

    + * + *
      + *
    • Override the {@link #supportsSystemMBeanServerForwarder()} + * method so that it returns true.

      + * + *
    • Call {@link #installStandardForwarders} from the constructor of + * the connector server.

      + * + *
    • Direct incoming requests to the result of {@link + * #getSystemMBeanServerForwarder()} instead of the result of {@link + * #getMBeanServer()}.

      + *
    + * * @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; - } - - /** - *

    Inserts an object that intercepts requests for the MBean server - * that arrive through this connector server. This object will be - * supplied as the MBeanServer for any new connection - * created by this connector server. Existing connections are - * unaffected.

    - * - *

    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.

    - * - *

    This method modifies the system chain of {@link MBeanServerForwarder}s. - * Usually user code should change the user chain instead, via - * {@link #setMBeanServerForwarder}.

    - * - *

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

    - * - *

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

    - * - *

    The result of {@link #getMBeanServer()} is unchanged by this method.

    - * - * @param mbsf the new MBeanServerForwarder. - * - * @throws IllegalArgumentException if the call to {@link - * MBeanServerForwarder#setMBeanServer mbsf.setMBeanServer} fails - * with IllegalArgumentException, or if - * mbsf is null. - * - * @throws UnsupportedOperationException if - * {@link #supportsSystemMBeanServerForwarder} returns false. - * - * @see #getSystemMBeanServer() - */ - public synchronized void setSystemMBeanServerForwarder( - MBeanServerForwarder mbsf) { - if (mbsf == null) - throw new IllegalArgumentException("Invalid null argument: mbsf"); - mustSupportSystemMBSF(); - - if (systemMBeanServer != null) - mbsf.setMBeanServer(systemMBeanServer); - systemMBeanServer = mbsf; + public MBeanServerForwarder getSystemMBeanServerForwarder() { + if (!supportsSystemMBeanServerForwarder()) { + throw new UnsupportedOperationException( + "System MBeanServerForwarder not supported by this " + + "connector server"); + } + return systemMBeanServerForwarder; } /** @@ -350,19 +379,13 @@ public abstract class JMXConnectorServer * * @return true if this connector server supports the system chain of * forwarders. + * + * @since 1.7 */ public boolean supportsSystemMBeanServerForwarder() { return false; } - private void mustSupportSystemMBSF() { - if (!supportsSystemMBeanServerForwarder()) { - throw new UnsupportedOperationException( - "System MBeanServerForwarder not supported by this " + - "connector server"); - } - } - /** *

    Install {@link MBeanServerForwarder}s in the system chain * based on the attributes in the given {@code Map}. A connector @@ -374,34 +397,90 @@ public abstract class JMXConnectorServer *

      * *
    • 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.
    • + * 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()}. + * + *
    • 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.
    • + * + *
    • 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.
    • * *
    * - *

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

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

    Since each forwarder is inserted at the start of the chain, + * the final order of the forwarders is the reverse 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.

    * *

    Attributes in {@code env} that are not listed above are ignored * by this method.

    * * @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 env) { - mustSupportSystemMBSF(); + MBeanServerForwarder sysMBSF = getSystemMBeanServerForwarder(); // Remember that forwarders must be added in reverse order! boolean ecd = EnvHelp.computeBooleanFromString( env, EVENT_CLIENT_DELEGATE_FORWARDER, false, true); + boolean localize = EnvHelp.computeBooleanFromString( + env, LOCALIZE_MBEAN_INFO_FORWARDER, false, false); + boolean context = EnvHelp.computeBooleanFromString( + env, CONTEXT_FORWARDER, false, true); + + if (localize && !context) { + throw new IllegalArgumentException( + "Inconsistent environment parameters: " + + LOCALIZE_MBEAN_INFO_FORWARDER + "=\"true\" requires " + + CONTEXT_FORWARDER + "=\"true\""); + } if (ecd) { - MBeanServerForwarder mbsf = EventClientDelegate.newForwarder(); - setSystemMBeanServerForwarder(mbsf); + MBeanServerForwarder mbsf = EventClientDelegate.newForwarder( + sysMBSF.getMBeanServer(), sysMBSF); + sysMBSF.setMBeanServer(mbsf); + } + + if (localize) { + MBeanServerForwarder mbsf = ClientContext.newLocalizeMBeanInfoForwarder( + sysMBSF.getMBeanServer()); + sysMBSF.setMBeanServer(mbsf); + } + + if (context) { + MBeanServerForwarder mbsf = ClientContext.newContextForwarder( + sysMBSF.getMBeanServer(), sysMBSF); + sysMBSF.setMBeanServer(mbsf); } } @@ -473,6 +552,7 @@ public abstract class JMXConnectorServer * * @return the array of possible notifications. */ + @Override public MBeanNotificationInfo[] getNotificationInfo() { final String[] types = { JMXConnectionNotification.OPENED, @@ -684,30 +764,29 @@ public abstract class JMXConnectorServer * Fields describing the chains of forwarders (MBeanServerForwarders). * In the general case, the forwarders look something like this: * - * systemMBeanServer userMBeanServer - * | | - * v v - * mbsf1 -> mbsf2 -> mbsf3 -> mbsf4 -> mbsf5 -> mbs + * userMBeanServer + * | + * v + * systemMBeanServerForwarder -> mbsf2 -> mbsf3 -> mbsf4 -> mbsf5 -> mbs * * Here, each mbsfi is an MBeanServerForwarder, and the arrows * illustrate its getMBeanServer() method. The last MBeanServerForwarder * can point to an MBeanServer that is not instanceof MBeanServerForwarder, * here mbs. * - * Initially, the chain can be empty if this JMXConnectorServer was - * constructed without an MBeanServer. In this case, both systemMBS - * and userMBS will be null. If there is initially an MBeanServer, - * then both systemMBS and userMBS will point to it. + * The system chain is never empty because it always has at least + * systemMBeanServerForwarder. Initially, the user chain can be empty if + * this JMXConnectorServer was constructed without an MBeanServer. In + * this case, userMBS will be null. If there is initially an MBeanServer, + * userMBS will point to it. * - * Whenever userMBS is changed, the system chain must be updated. If there - * are forwarders in the system chain (between systemMBS and userMBS in the - * picture above), then the last one must point to the old value of userMBS - * (possibly null). It must be updated to point to the new value. If there - * are no forwarders in the system chain, then systemMBS must be updated to - * the new value of userMBS. The invariant is that starting from systemMBS - * and repeatedly calling MBSF.getMBeanServer() you will end up at - * userMBS. The implication is that you will not see any MBeanServer - * object on the way that is not also an MBeanServerForwarder. + * Whenever userMBS is changed, the system chain must be updated. Before + * the update, the last forwarder in the system chain points to the old + * value of userMBS (possibly null). It must be updated to point to + * the new value. The invariant is that starting from systemMBSF and + * repeatedly calling MBSF.getMBeanServer() you will end up at userMBS. + * The implication is that you will not see any MBeanServer object on the + * way that is not also an MBeanServerForwarder. * * The method insertUserMBeanServer contains the logic to change userMBS * and adjust the system chain appropriately. @@ -716,7 +795,7 @@ public abstract class JMXConnectorServer * MBeanServer, then userMBS becomes that MBeanServer, and the system * chain must be updated as just described. * - * When systemMBS is updated, there is no effect on userMBS. The system + * When systemMBSF is updated, there is no effect on userMBS. The system * chain may contain forwarders even though the user chain is empty * (there is no MBeanServer). In that case an attempt to forward an * incoming request through the chain will fall off the end and fail with a @@ -726,7 +805,8 @@ public abstract class JMXConnectorServer private MBeanServer userMBeanServer; - private MBeanServer systemMBeanServer; + private final MBeanServerForwarder systemMBeanServerForwarder = + new IdentityMBeanServerForwarder(); /** * The name used to registered this server in an MBeanServer. diff --git a/jdk/src/share/classes/javax/management/remote/JMXConnectorServerMBean.java b/jdk/src/share/classes/javax/management/remote/JMXConnectorServerMBean.java index fb6f883a2b7..b784b87e48c 100644 --- a/jdk/src/share/classes/javax/management/remote/JMXConnectorServerMBean.java +++ b/jdk/src/share/classes/javax/management/remote/JMXConnectorServerMBean.java @@ -132,7 +132,7 @@ public interface JMXConnectorServerMBean { * *

    A connector server may support two chains of forwarders, * a system chain and a user chain. See {@link - * JMXConnectorServer#setSystemMBeanServerForwarder} for details.

    + * JMXConnectorServer#getSystemMBeanServerForwarder} for details.

    * * @param mbsf the new MBeanServerForwarder. * @@ -141,7 +141,7 @@ public interface JMXConnectorServerMBean { * with IllegalArgumentException. This includes the * case where mbsf is null. * - * @see JMXConnectorServer#setSystemMBeanServerForwarder + * @see JMXConnectorServer#getSystemMBeanServerForwarder */ public void setMBeanServerForwarder(MBeanServerForwarder mbsf); diff --git a/jdk/src/share/classes/javax/management/remote/rmi/RMIConnector.java b/jdk/src/share/classes/javax/management/remote/rmi/RMIConnector.java index 1f17143b948..5f1b8ac71ce 100644 --- a/jdk/src/share/classes/javax/management/remote/rmi/RMIConnector.java +++ b/jdk/src/share/classes/javax/management/remote/rmi/RMIConnector.java @@ -134,14 +134,14 @@ import sun.rmi.transport.LiveRef; 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 environment) { + Map 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 class RMIConnector implements JMXConnector, Serializable, JMXAddressable } public synchronized void connect(Map environment) - throws IOException { + throws IOException { final boolean tracing = logger.traceOn(); String idstr = (tracing?"["+this.toString()+"]":null); @@ -274,8 +274,8 @@ public class RMIConnector implements JMXConnector, Serializable, JMXAddressable if (tracing) logger.trace("connect",idstr + " connecting..."); final Map usemap = - new HashMap((this.env==null) ? - Collections.emptyMap() : this.env); + new HashMap((this.env==null) ? + Collections.emptyMap() : this.env); if (environment != null) { @@ -315,7 +315,7 @@ public class RMIConnector implements JMXConnector, Serializable, JMXAddressable defaultClassLoader = EnvHelp.resolveClientClassLoader(usemap); usemap.put(JMXConnectorFactory.DEFAULT_CLASS_LOADER, - defaultClassLoader); + defaultClassLoader); rmiNotifClient = new RMINotifClient(defaultClassLoader, usemap); @@ -333,12 +333,12 @@ public class RMIConnector implements JMXConnector, Serializable, JMXAddressable 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 @@ public class RMIConnector implements JMXConnector, Serializable, JMXAddressable 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 class RMIConnector implements JMXConnector, Serializable, JMXAddressable } 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 @@ public class RMIConnector implements JMXConnector, Serializable, JMXAddressable 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 class RMIConnector implements JMXConnector, Serializable, JMXAddressable } 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 class RMIConnector implements JMXConnector, Serializable, JMXAddressable } 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 @@ public class RMIConnector implements JMXConnector, Serializable, JMXAddressable 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 @@ public class RMIConnector implements JMXConnector, Serializable, JMXAddressable } 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 @@ public class RMIConnector implements JMXConnector, Serializable, JMXAddressable 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 @@ public class RMIConnector implements JMXConnector, Serializable, JMXAddressable // 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 @@ public class RMIConnector implements JMXConnector, Serializable, JMXAddressable final boolean debug = logger.debugOn(); if (debug) logger.debug("addListenerWithSubject", - "(ObjectName,MarshalledObject,Subject)"); + "(ObjectName,MarshalledObject,Subject)"); final ObjectName[] names = new ObjectName[] {name}; final MarshalledObject[] filters = @@ -603,11 +603,11 @@ public class RMIConnector implements JMXConnector, Serializable, JMXAddressable }; 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 @@ public class RMIConnector implements JMXConnector, Serializable, JMXAddressable 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 class RMIConnector implements JMXConnector, Serializable, JMXAddressable } 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 class RMIConnector implements JMXConnector, Serializable, JMXAddressable } 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 sParams = new MarshalledObject(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 sParams = new MarshalledObject(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 class RMIConnector implements JMXConnector, Serializable, JMXAddressable } public ObjectInstance getObjectInstance(ObjectName name) - throws InstanceNotFoundException, - IOException { + throws InstanceNotFoundException, + IOException { if (logger.debugOn()) logger.debug("getObjectInstance", "name=" + name); @@ -855,10 +855,10 @@ public class RMIConnector implements JMXConnector, Serializable, JMXAddressable } public Set 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 sQuery = new MarshalledObject(query); @@ -875,10 +875,10 @@ public class RMIConnector implements JMXConnector, Serializable, JMXAddressable } public Set 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 sQuery = new MarshalledObject(query); @@ -895,7 +895,7 @@ public class RMIConnector implements JMXConnector, Serializable, JMXAddressable } public boolean isRegistered(ObjectName name) - throws IOException { + throws IOException { if (logger.debugOn()) logger.debug("isRegistered", "name=" + name); @@ -912,7 +912,7 @@ public class RMIConnector implements JMXConnector, Serializable, JMXAddressable } public Integer getMBeanCount() - throws IOException { + throws IOException { if (logger.debugOn()) logger.debug("getMBeanCount", ""); final ClassLoader old = pushDefaultClassLoader(); @@ -928,53 +928,53 @@ public class RMIConnector implements JMXConnector, Serializable, JMXAddressable } 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 class RMIConnector implements JMXConnector, Serializable, JMXAddressable 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 sAttribute = - new MarshalledObject(attribute); + new MarshalledObject(attribute); final ClassLoader old = pushDefaultClassLoader(); try { connection.setAttribute(name, sAttribute, delegationSubject); @@ -1009,28 +1009,28 @@ public class RMIConnector implements JMXConnector, Serializable, JMXAddressable } 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 sAttributes = - new MarshalledObject(attributes); + new MarshalledObject(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 class RMIConnector implements JMXConnector, Serializable, JMXAddressable 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 sParams = new MarshalledObject(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 class RMIConnector implements JMXConnector, Serializable, JMXAddressable public String getDefaultDomain() - throws IOException { + throws IOException { if (logger.debugOn()) logger.debug("getDefaultDomain", ""); final ClassLoader old = pushDefaultClassLoader(); @@ -1107,10 +1107,10 @@ public class RMIConnector implements JMXConnector, Serializable, JMXAddressable } 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 class RMIConnector implements JMXConnector, Serializable, JMXAddressable 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 sFilter = new MarshalledObject(filter); @@ -1170,64 +1170,64 @@ public class RMIConnector implements JMXConnector, Serializable, JMXAddressable 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 sFilter = new MarshalledObject(filter); @@ -1236,18 +1236,18 @@ public class RMIConnector implements JMXConnector, Serializable, JMXAddressable 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 @@ public class RMIConnector implements JMXConnector, Serializable, JMXAddressable // 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(filter), - delegationSubject,true); + addListenerWithSubject(name, + new MarshalledObject(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 @@ public class RMIConnector implements JMXConnector, Serializable, JMXAddressable 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 class RMIConnector implements JMXConnector, Serializable, JMXAddressable } 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 @@ public class RMIConnector implements JMXConnector, Serializable, JMXAddressable } 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 @@ public class RMIConnector implements JMXConnector, Serializable, JMXAddressable 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 @@ public class RMIConnector implements JMXConnector, Serializable, JMXAddressable } protected Integer addListenerForMBeanRemovedNotif() - throws IOException, InstanceNotFoundException { + throws IOException, InstanceNotFoundException { NotificationFilterSupport clientFilter = - new NotificationFilterSupport(); + new NotificationFilterSupport(); clientFilter.enableType( - MBeanServerNotification.UNREGISTRATION_NOTIFICATION); + MBeanServerNotification.UNREGISTRATION_NOTIFICATION); MarshalledObject sFilter = new MarshalledObject(clientFilter); @@ -1451,36 +1451,36 @@ public class RMIConnector implements JMXConnector, Serializable, JMXAddressable 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 @@ public class RMIConnector implements JMXConnector, Serializable, JMXAddressable // 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 @@ public class RMIConnector implements JMXConnector, Serializable, JMXAddressable 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 @@ public class RMIConnector implements JMXConnector, Serializable, JMXAddressable for (i=0;i(newOrb); return newOrb; } @@ -1810,11 +1810,11 @@ public class RMIConnector implements JMXConnector, Serializable, JMXAddressable * @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 @@ public class RMIConnector implements JMXConnector, Serializable, JMXAddressable * @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 @@ public class RMIConnector implements JMXConnector, Serializable, JMXAddressable //-------------------------------------------------------------------- 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 @@ public class RMIConnector implements JMXConnector, Serializable, JMXAddressable 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 @@ public class RMIConnector implements JMXConnector, Serializable, JMXAddressable 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 @@ public class RMIConnector implements JMXConnector, Serializable, JMXAddressable //-------------------------------------------------------------------- private RMIServer findRMIServer(JMXServiceURL directoryURL, - Map environment) - throws NamingException, IOException { + Map 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 @@ public class RMIConnector implements JMXConnector, Serializable, JMXAddressable 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 @@ public class RMIConnector implements JMXConnector, Serializable, JMXAddressable * @exception NamingException if the stub couldn't be found. **/ private RMIServer findRMIServerJNDI(String jndiURL, Map env, - boolean isIiop) - throws NamingException { + boolean isIiop) + throws NamingException { InitialContext ctx = new InitialContext(EnvHelp.mapToHashtable(env)); @@ -1997,11 +1997,11 @@ public class RMIConnector implements JMXConnector, Serializable, JMXAddressable 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 @@ public class RMIConnector implements JMXConnector, Serializable, JMXAddressable private RMIServer findRMIServerIIOP(String ior, Map 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 @@ public class RMIConnector implements JMXConnector, Serializable, JMXAddressable 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 @@ public class RMIConnector implements JMXConnector, Serializable, JMXAddressable 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 @@ public class RMIConnector implements JMXConnector, Serializable, JMXAddressable 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\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\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> action = new PrivilegedExceptionAction>() { 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 @@ public class RMIConnector implements JMXConnector, Serializable, JMXAddressable serverStubClass = Class.forName(rmiServerImplStubClassName); } catch (Exception e) { logger.error("", - "Failed to instantiate " + - rmiServerImplStubClassName + ": " + e); + "Failed to instantiate " + + rmiServerImplStubClassName + ": " + e); logger.debug("",e); serverStubClass = null; } @@ -2185,8 +2185,8 @@ public class RMIConnector implements JMXConnector, Serializable, JMXAddressable constr = (Constructor) AccessController.doPrivileged(action); } catch (Exception e) { logger.error("", - "Failed to initialize proxy reference constructor "+ - "for " + rmiConnectionImplStubClassName + ": " + e); + "Failed to initialize proxy reference constructor "+ + "for " + rmiConnectionImplStubClassName + ": " + e); logger.debug("",e); stubClass = null; constr = null; @@ -2196,9 +2196,9 @@ public class RMIConnector implements JMXConnector, Serializable, JMXAddressable } 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 @@ public class RMIConnector implements JMXConnector, Serializable, JMXAddressable rmiConnectionImplStubClass.getConstructor(RemoteRef.class); Object[] args = {proxyRef}; RMIConnection proxyStub = (RMIConnection) - rmiConnectionImplStubConstructor.newInstance(args); + rmiConnectionImplStubConstructor.newInstance(args); return proxyStub; } @@ -2326,55 +2326,55 @@ public class RMIConnector implements JMXConnector, Serializable, JMXAddressable */ 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\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\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\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\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 @@ public class RMIConnector implements JMXConnector, Serializable, JMXAddressable 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 @@ public class RMIConnector implements JMXConnector, Serializable, JMXAddressable stubClass = AccessController.doPrivileged(action); } catch (Exception e) { logger.error("", - "Unexpected exception making shadow IIOP stub class: "+e); + "Unexpected exception making shadow IIOP stub class: "+e); logger.debug("",e); stubClass = null; } @@ -2411,15 +2411,15 @@ public class RMIConnector implements JMXConnector, Serializable, JMXAddressable } 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 @@ public class RMIConnector implements JMXConnector, Serializable, JMXAddressable 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 @@ public class RMIConnector implements JMXConnector, Serializable, JMXAddressable 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 @@ public class RMIConnector implements JMXConnector, Serializable, JMXAddressable final ClassLoader old = t.getContextClassLoader(); if (defaultClassLoader != null) AccessController.doPrivileged(new PrivilegedAction() { - 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() { - public Void run() { - Thread.currentThread().setContextClassLoader(old); - return null; - } - }); + public Void run() { + Thread.currentThread().setContextClassLoader(old); + return null; + } + }); } //-------------------------------------------------------------------- diff --git a/jdk/src/share/classes/javax/management/remote/rmi/RMIConnectorServer.java b/jdk/src/share/classes/javax/management/remote/rmi/RMIConnectorServer.java index e56020b4722..9d38c7e8944 100644 --- a/jdk/src/share/classes/javax/management/remote/rmi/RMIConnectorServer.java +++ b/jdk/src/share/classes/javax/management/remote/rmi/RMIConnectorServer.java @@ -383,7 +383,7 @@ public class RMIConnectorServer extends JMXConnectorServer { try { if (tracing) logger.trace("start", "setting default class loader"); defaultClassLoader = EnvHelp.resolveServerClassLoader( - attributes, getSystemMBeanServer()); + attributes, getSystemMBeanServerForwarder()); } catch (InstanceNotFoundException infc) { IllegalArgumentException x = new IllegalArgumentException("ClassLoader not found: "+infc); @@ -398,7 +398,7 @@ public class RMIConnectorServer extends JMXConnectorServer { else rmiServer = newServer(); - rmiServer.setMBeanServer(getSystemMBeanServer()); + rmiServer.setMBeanServer(getSystemMBeanServerForwarder()); rmiServer.setDefaultClassLoader(defaultClassLoader); rmiServer.setRMIConnectorServer(this); rmiServer.export(); @@ -592,31 +592,6 @@ public class RMIConnectorServer extends JMXConnectorServer { return Collections.unmodifiableMap(map); } - @Override - public synchronized void setMBeanServerForwarder(MBeanServerForwarder mbsf) { - MBeanServer oldSMBS = getSystemMBeanServer(); - super.setMBeanServerForwarder(mbsf); - if (oldSMBS != getSystemMBeanServer()) - updateMBeanServer(); - // If the system chain of MBeanServerForwarders is not empty, then - // there is no need to call rmiServerImpl.setMBeanServer, because - // it is pointing to the head of the system chain and that has not - // changed. (The *end* of the system chain will have been changed - // to point to mbsf.) - } - - private void updateMBeanServer() { - if (rmiServerImpl != null) - rmiServerImpl.setMBeanServer(getSystemMBeanServer()); - } - - @Override - public synchronized void setSystemMBeanServerForwarder( - MBeanServerForwarder mbsf) { - super.setSystemMBeanServerForwarder(mbsf); - updateMBeanServer(); - } - /** * {@inheritDoc} * @return true, since this connector server does support a system chain @@ -631,16 +606,19 @@ public class RMIConnectorServer extends JMXConnectorServer { here so that they are accessible to other classes in this package even though they have protected access. */ + @Override protected void connectionOpened(String connectionId, String message, Object userData) { super.connectionOpened(connectionId, message, userData); } + @Override protected void connectionClosed(String connectionId, String message, Object userData) { super.connectionClosed(connectionId, message, userData); } + @Override protected void connectionFailed(String connectionId, String message, Object userData) { super.connectionFailed(connectionId, message, userData); diff --git a/jdk/test/javax/management/Introspector/AnnotationTest.java b/jdk/test/javax/management/Introspector/AnnotationTest.java index e6d3c10b042..49ca0d3405c 100644 --- a/jdk/test/javax/management/Introspector/AnnotationTest.java +++ b/jdk/test/javax/management/Introspector/AnnotationTest.java @@ -39,7 +39,8 @@ import javax.management.*; /* This test checks that annotations produce Descriptor entries as - specified in javax.management.DescriptorKey. It does two things: + specified in javax.management.DescriptorKey and javax.management.DescriptorField. + It does the following: - An annotation consisting of an int and a String, each with an appropriate @DescriptorKey annotation, is placed on every program @@ -61,6 +62,10 @@ import javax.management.*; The test checks that in each case the corresponding Descriptor appears in the appropriate place inside the MBean's MBeanInfo. + - A @DescriptorFields annotation defining two fields is placed in the + same places and again the test checks that the two fields appear + in the corresponding MBean*Info objects. + - An annotation consisting of enough other types to ensure coverage is placed on a getter. The test checks that the generated MBeanAttributeInfo contains the corresponding Descriptor. The tested @@ -78,12 +83,6 @@ import javax.management.*; public class AnnotationTest { private static String failed = null; -// @Retention(RetentionPolicy.RUNTIME) @Inherited -// @Target(ElementType.METHOD) -// public static @interface DescriptorKey { -// String value(); -// } - @Retention(RetentionPolicy.RUNTIME) public static @interface Pair { @DescriptorKey("x") @@ -112,11 +111,12 @@ public class AnnotationTest { boolean[] booleanArrayValue(); } - /* We use the annotation @Pair(x = 3, y = "foo") everywhere, and this is - the Descriptor that it should produce: */ + /* We use the annotations @Pair(x = 3, y = "foo") + and @DescriptorFields({"foo=bar", "baz="}) everywhere, and this is + the Descriptor that they should produce: */ private static Descriptor expectedDescriptor = - new ImmutableDescriptor(new String[] {"x", "y"}, - new Object[] {3, "foo"}); + new ImmutableDescriptor(new String[] {"x", "y", "foo", "baz"}, + new Object[] {3, "foo", "bar", ""}); private static Descriptor expectedFullDescriptor = new ImmutableDescriptor(new String[] { @@ -136,8 +136,10 @@ public class AnnotationTest { }); @Pair(x = 3, y = "foo") + @DescriptorFields({"foo=bar", "baz="}) public static interface ThingMBean { @Pair(x = 3, y = "foo") + @DescriptorFields({"foo=bar", "baz="}) @Full(classValue=Full.class, enumValue=RetentionPolicy.RUNTIME, booleanValue=false, @@ -149,32 +151,47 @@ public class AnnotationTest { int getReadOnly(); @Pair(x = 3, y = "foo") + @DescriptorFields({"foo=bar", "baz="}) void setWriteOnly(int x); @Pair(x = 3, y = "foo") + @DescriptorFields({"foo=bar", "baz="}) int getReadWrite1(); void setReadWrite1(int x); @Pair(x = 3, y = "foo") + @DescriptorFields({"foo=bar", "baz="}) int getReadWrite2(); @Pair(x = 3, y = "foo") + @DescriptorFields({"foo=bar", "baz="}) void setReadWrite2(int x); int getReadWrite3(); @Pair(x = 3, y = "foo") + @DescriptorFields({"foo=bar", "baz="}) void setReadWrite3(int x); @Pair(x = 3, y = "foo") - int operation(@Pair(x = 3, y = "foo") int p1, - @Pair(x = 3, y = "foo") int p2); + @DescriptorFields({"foo=bar", "baz="}) + int operation(@Pair(x = 3, y = "foo") + @DescriptorFields({"foo=bar", "baz="}) + int p1, + @Pair(x = 3, y = "foo") + @DescriptorFields({"foo=bar", "baz="}) + int p2); } public static class Thing implements ThingMBean { @Pair(x = 3, y = "foo") + @DescriptorFields({"foo=bar", "baz="}) public Thing() {} @Pair(x = 3, y = "foo") - public Thing(@Pair(x = 3, y = "foo") int p1) {} + @DescriptorFields({"foo=bar", "baz="}) + public Thing( + @Pair(x = 3, y = "foo") + @DescriptorFields({"foo=bar", "baz="}) + int p1) {} public int getReadOnly() {return 0;} @@ -193,14 +210,20 @@ public class AnnotationTest { } @Pair(x = 3, y = "foo") + @DescriptorFields({"foo=bar", "baz="}) public static interface ThingMXBean extends ThingMBean {} public static class ThingImpl implements ThingMXBean { @Pair(x = 3, y = "foo") + @DescriptorFields({"foo=bar", "baz="}) public ThingImpl() {} @Pair(x = 3, y = "foo") - public ThingImpl(@Pair(x = 3, y = "foo") int p1) {} + @DescriptorFields({"foo=bar", "baz="}) + public ThingImpl( + @Pair(x = 3, y = "foo") + @DescriptorFields({"foo=bar", "baz="}) + int p1) {} public int getReadOnly() {return 0;} @@ -218,6 +241,79 @@ public class AnnotationTest { public int operation(int p1, int p2) {return 0;} } + @Retention(RetentionPolicy.RUNTIME) + public static @interface DefaultTest { + @DescriptorKey(value = "string1", omitIfDefault = true) + String string1() default ""; + @DescriptorKey(value = "string2", omitIfDefault = true) + String string2() default "tiddly pom"; + @DescriptorKey(value = "int", omitIfDefault = true) + int intx() default 23; + @DescriptorKey(value = "intarray1", omitIfDefault = true) + int[] intArray1() default {}; + @DescriptorKey(value = "intarray2", omitIfDefault = true) + int[] intArray2() default {1, 2}; + @DescriptorKey(value = "stringarray1", omitIfDefault = true) + String[] stringArray1() default {}; + @DescriptorKey(value = "stringarray2", omitIfDefault = true) + String[] stringArray2() default {"foo", "bar"}; + } + + @Retention(RetentionPolicy.RUNTIME) + public static @interface Expect { + String[] value() default {}; + } + + public static interface DefaultMBean { + @DefaultTest + @Expect() + public void a(); + + @DefaultTest(string1="") + @Expect() + public void b(); + + @DefaultTest(string1="nondefault") + @Expect("string1=nondefault") + public void c(); + + @DefaultTest(string2="tiddly pom") + @Expect() + public void d(); + + @DefaultTest(intx=23) + @Expect() + public void e(); + + @DefaultTest(intx=34) + @Expect("int=34") + public void f(); + + @DefaultTest(intArray1={}) + @Expect() + public void g(); + + @DefaultTest(intArray1={2,3}) + @Expect("intarray1=[2, 3]") + public void h(); + + @DefaultTest(intArray2={}) + @Expect("intarray2=[]") + public void i(); + + @DefaultTest(stringArray1={}) + @Expect() + public void j(); + + @DefaultTest(stringArray1={"foo"}) + @Expect("stringarray1=[foo]") + public void k(); + + @DefaultTest(stringArray2={}) + @Expect("stringarray2=[]") + public void l(); + } + public static void main(String[] args) throws Exception { System.out.println("Testing that annotations are correctly " + "reflected in Descriptor entries"); @@ -225,20 +321,62 @@ public class AnnotationTest { MBeanServer mbs = java.lang.management.ManagementFactory.getPlatformMBeanServer(); ObjectName on = new ObjectName("a:b=c"); + Thing thing = new Thing(); mbs.registerMBean(thing, on); check(mbs, on); mbs.unregisterMBean(on); + ThingImpl thingImpl = new ThingImpl(); mbs.registerMBean(thingImpl, on); + Descriptor d = mbs.getMBeanInfo(on).getDescriptor(); + if (!d.getFieldValue("mxbean").equals("true")) { + System.out.println("NOT OK: expected MXBean"); + failed = "Expected MXBean"; + } check(mbs, on); + System.out.println(); + System.out.println("Testing that omitIfDefault works"); + DefaultMBean defaultImpl = (DefaultMBean) Proxy.newProxyInstance( + DefaultMBean.class.getClassLoader(), + new Class[] {DefaultMBean.class}, + new InvocationHandler(){ + public Object invoke(Object proxy, Method method, Object[] args) { + return null; + } + }); + DynamicMBean mbean = new StandardMBean(defaultImpl, DefaultMBean.class); + MBeanOperationInfo[] ops = mbean.getMBeanInfo().getOperations(); + for (MBeanOperationInfo op : ops) { + String name = op.getName(); + Expect expect = + DefaultMBean.class.getMethod(name).getAnnotation(Expect.class); + Descriptor opd = op.getDescriptor(); + List fields = new ArrayList(); + for (String fieldName : opd.getFieldNames()) { + Object value = opd.getFieldValue(fieldName); + String s = Arrays.deepToString(new Object[] {value}); + s = s.substring(1, s.length() - 1); + fields.add(fieldName + "=" + s); + } + Descriptor opds = new ImmutableDescriptor(fields.toArray(new String[0])); + Descriptor expd = new ImmutableDescriptor(expect.value()); + if (opds.equals(expd)) + System.out.println("OK: op " + name + ": " + opds); + else { + String failure = "Bad descriptor for op " + name + ": " + + "expected " + expd + ", got " + opds; + System.out.println("NOT OK: " + failure); + failed = failure; + } + } + System.out.println(); + if (failed == null) System.out.println("Test passed"); - else if (true) - throw new Exception("TEST FAILED: " + failed); else - System.out.println("Test disabled until 6221321 implemented"); + throw new Exception("TEST FAILED: " + failed); } private static void check(MBeanServer mbs, ObjectName on) throws Exception { @@ -295,151 +433,4 @@ public class AnnotationTest { for (DescriptorRead x : xx) check(x); } - - public static class AnnotatedMBean extends StandardMBean { - AnnotatedMBean(T resource, Class 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 descriptorMap = new HashMap(); - for (Annotation a : annots) { - Class 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> primitives = - new HashMap>(); - 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); - } - } - } } diff --git a/jdk/test/javax/management/context/ContextForwarderTest.java b/jdk/test/javax/management/context/ContextForwarderTest.java new file mode 100644 index 00000000000..d82acd3b63e --- /dev/null +++ b/jdk/test/javax/management/context/ContextForwarderTest.java @@ -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 env = new HashMap(); + 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 localNames = + new TreeSet(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 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); + } +} diff --git a/jdk/test/javax/management/context/ContextTest.java b/jdk/test/javax/management/context/ContextTest.java new file mode 100644 index 00000000000..1fb293ef574 --- /dev/null +++ b/jdk/test/javax/management/context/ContextTest.java @@ -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 emptyContext = emptyMap(); + + public static interface ShowContextMBean { + public Map getContext(); + public Map getCreationContext(); + public Set 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 creationContext; + private final Set calledOps = new HashSet(); + + public ShowContext() { + creationContext = getContext(); + } + + public Map getContext() { + return ClientContext.getContext(); + } + + public Map getCreationContext() { + return creationContext; + } + + public Set 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 context; + LogRecord(String op, Object[] params, Map 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 log = new LinkedList(); + + 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 newSnoop(Class 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() { + 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() { + public String call() { + return ClientContext.getContext().get("foo"); + } + }); + assertEquals("value extracted from context", "baz", got); + + Map combined = ClientContext.doWithContext( + singletonMap("foo", "baz"), new Callable>() { + public Map call() throws Exception { + return ClientContext.doWithContext( + singletonMap("fred", "jim"), + new Callable>() { + public Map call() { + return ClientContext.getContext(); + } + }); + } + }); + assertEquals("nested doWithContext context", + singletonMap("fred", "jim"), combined); + + final String ugh = "a!\u00c9//*=:\"% "; + ClientContext.doWithContext(singletonMap(ugh, ugh), new Callable() { + public Void call() { + assertEquals("context with tricky encoding", + singletonMap(ugh, ugh), show.getContext()); + return null; + } + }); + Map ughMap = new TreeMap(); + 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 origNames = mbs.queryNames(null, null); + Set 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 domains = new HashSet(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 ughCtx = ughshow.getContext(); + Map 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 disney = new HashMap(); + 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 names = ccmbs.queryNames(null, null); + Set expectedNames = new HashSet( + Collections.singleton(MBeanServerDelegate.DELEGATE_NAME)); + assertEquals("context namespace query includes expected names", + true, names.containsAll(expectedNames)); + + Set nsNames = ccmbs.queryNames(new ObjectName("*//:*"), null); + Set expectedNsNames = new HashSet( + Arrays.asList( + new ObjectName(ClientContext.NAMESPACE + + ObjectName.NAMESPACE_SEPARATOR + ":" + + JMXNamespace.TYPE_ASSIGNMENT))); + assertEquals("context namespace query includes namespace MBean", + true, nsNames.containsAll(expectedNsNames)); + + + + Set 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 expectedOps = new HashSet(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> badEncodeArgs = + Arrays.asList( + null, + Collections.singletonMap(null, "foo"), + Collections.singletonMap("foo", null)); + for (Map 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 expectedContext) { + assertCalled(logIH, op, expectedContext, (Object[]) null); + } + + private static void assertCalled( + LogIH logIH, String op, Map 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); + } +} diff --git a/jdk/test/javax/management/context/LocaleAwareBroadcasterTest.java b/jdk/test/javax/management/context/LocaleAwareBroadcasterTest.java new file mode 100644 index 00000000000..7963f9f33b6 --- /dev/null +++ b/jdk/test/javax/management/context/LocaleAwareBroadcasterTest.java @@ -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 + localeToEmitter = newConcurrentMap(); + + public void sendNotification(Notification n) { + for (Map.Entry 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 localeContext = Collections.singletonMap( + ClientContext.LOCALE_KEY, locale.toString()); + ClientContext.doWithContext( + localeContext, new Callable() { + 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 queue = + new ArrayBlockingQueue(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 ConcurrentMap newConcurrentMap() { + return new ConcurrentHashMap(); + } + + static void fail(String why) { + System.out.println("FAIL: " + why); + failure = why; + } +} diff --git a/jdk/test/javax/management/context/LocaleTest.java b/jdk/test/javax/management/context/LocaleTest.java new file mode 100644 index 00000000000..e5ea5309c21 --- /dev/null +++ b/jdk/test/javax/management/context/LocaleTest.java @@ -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 ctx = Collections.singletonMap( + ClientContext.LOCALE_KEY, loc.toString()); + Locale loc2 = ClientContext.doWithContext( + ctx, new Callable() { + 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); + } +} diff --git a/jdk/test/javax/management/context/LocalizableTest.java b/jdk/test/javax/management/context/LocalizableTest.java new file mode 100644 index 00000000000..40a18af350d --- /dev/null +++ b/jdk/test/javax/management/context/LocalizableTest.java @@ -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; + } +} diff --git a/jdk/test/javax/management/context/RemoteContextTest.java b/jdk/test/javax/management/context/RemoteContextTest.java new file mode 100644 index 00000000000..56c7eab6c8b --- /dev/null +++ b/jdk/test/javax/management/context/RemoteContextTest.java @@ -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 getContext(); + public Map getCreationContext(); + public Set 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 creationContext; + private final Set calledOps = new HashSet(); + + public ShowContext() { + creationContext = getContext(); + } + + public Map getContext() { + return ClientContext.getContext(); + } + + public Map getCreationContext() { + return creationContext; + } + + public Set 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 context; + LogRecord(String op, Object[] params, Map 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 log = new LinkedList(); + + 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 newSnoop(Class 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 env = Collections.emptyMap(); + final Map 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() { + public Void call() { + assertEquals(singletonMap("foo", "bar"), show.getContext()); + return null; + } + }); + assertEquals(emptyContext, show.getContext()); + String got = ClientContext.doWithContext( + singletonMap("foo", "baz"), new Callable() { + public String call() { + return ClientContext.getContext().get("foo"); + } + }); + assertEquals("baz", got); + + Map combined = ClientContext.doWithContext( + singletonMap("foo", "baz"), new Callable>() { + public Map call() throws Exception { + return ClientContext.doWithContext( + singletonMap("fred", "jim"), + new Callable>() { + public Map call() { + return ClientContext.getContext(); + } + }); + } + }); + assertEquals(singletonMap("fred", "jim"), combined); + + final String ugh = "a!?//*=:\"% "; + ClientContext.doWithContext(singletonMap(ugh, ugh), new Callable() { + 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 expectedDynamic = + Collections.singletonMap("gladstone", "gander"); + Map dynamic = ClientContext.doWithContext( + expectedDynamic, + new Callable>() { + public Map 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 disney = new HashMap(); + 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 names = ccmbs.queryNames(null, null); + Set expectedNames = new HashSet( + Collections.singleton(MBeanServerDelegate.DELEGATE_NAME)); + expectedNames.removeAll(names); + assertEquals(0, expectedNames.size()); + + Set nsNames = + ccmbs.queryNames(new ObjectName("**?*?//:*"), null); + Set expectedNsNames = new HashSet( + Arrays.asList( + new ObjectName(ClientContext.NAMESPACE + + ObjectName.NAMESPACE_SEPARATOR + ":" + + JMXNamespace.TYPE_ASSIGNMENT))); + expectedNsNames.removeAll(nsNames); + assertEquals(0, expectedNsNames.size()); + + Set 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 expectedOps = new HashSet(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 expectedContext) { + assertCalled(logIH, op, null, expectedContext); + } + + private static void assertCalled( + LogIH logIH, String op, Object[] params, + Map 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); + } +} diff --git a/jdk/test/javax/management/context/localizable/MBeanDescriptions.properties b/jdk/test/javax/management/context/localizable/MBeanDescriptions.properties new file mode 100644 index 00000000000..4722c5b5acc --- /dev/null +++ b/jdk/test/javax/management/context/localizable/MBeanDescriptions.properties @@ -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. + diff --git a/jdk/test/javax/management/context/localizable/MBeanDescriptions_fr.java b/jdk/test/javax/management/context/localizable/MBeanDescriptions_fr.java new file mode 100644 index 00000000000..9e9cfa2ffea --- /dev/null +++ b/jdk/test/javax/management/context/localizable/MBeanDescriptions_fr.java @@ -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}, + }; + } +} diff --git a/jdk/test/javax/management/context/localizable/Whatsit.java b/jdk/test/javax/management/context/localizable/Whatsit.java new file mode 100644 index 00000000000..f140ad920cb --- /dev/null +++ b/jdk/test/javax/management/context/localizable/Whatsit.java @@ -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; + } +} diff --git a/jdk/test/javax/management/context/localizable/WhatsitMBean.java b/jdk/test/javax/management/context/localizable/WhatsitMBean.java new file mode 100644 index 00000000000..5bebc52f312 --- /dev/null +++ b/jdk/test/javax/management/context/localizable/WhatsitMBean.java @@ -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); +} diff --git a/jdk/test/javax/management/eventService/CustomForwarderTest.java b/jdk/test/javax/management/eventService/CustomForwarderTest.java index c9c866aaee0..4d7e3027e31 100644 --- a/jdk/test/javax/management/eventService/CustomForwarderTest.java +++ b/jdk/test/javax/management/eventService/CustomForwarderTest.java @@ -200,8 +200,7 @@ public class CustomForwarderTest { public static void main(String[] args) throws Exception { MBeanServer mbs = ManagementFactory.getPlatformMBeanServer(); - MBeanServerForwarder mbsf = EventClientDelegate.newForwarder(); - mbsf.setMBeanServer(mbs); + MBeanServerForwarder mbsf = EventClientDelegate.newForwarder(mbs, null); mbs = mbsf; // for 1.5 diff --git a/jdk/test/javax/management/eventService/EventClientExecutorTest.java b/jdk/test/javax/management/eventService/EventClientExecutorTest.java index 19d6afdf814..5201335e8b9 100644 --- a/jdk/test/javax/management/eventService/EventClientExecutorTest.java +++ b/jdk/test/javax/management/eventService/EventClientExecutorTest.java @@ -65,8 +65,7 @@ public class EventClientExecutorTest { new NamedThreadFactory("LEASE")); MBeanServer mbs = MBeanServerFactory.newMBeanServer(); - MBeanServerForwarder mbsf = EventClientDelegate.newForwarder(); - mbsf.setMBeanServer(mbs); + MBeanServerForwarder mbsf = EventClientDelegate.newForwarder(mbs, null); mbs = mbsf; EventClientDelegateMBean ecd = EventClientDelegate.getProxy(mbs); diff --git a/jdk/test/javax/management/eventService/EventManagerTest.java b/jdk/test/javax/management/eventService/EventManagerTest.java index 2717c0e5103..66e4e36cf4a 100644 --- a/jdk/test/javax/management/eventService/EventManagerTest.java +++ b/jdk/test/javax/management/eventService/EventManagerTest.java @@ -98,7 +98,7 @@ public class EventManagerTest { succeed &= test(new EventClient(ecd, new RMIPushEventRelay(ecd), null, null, - EventClient.DEFAULT_LEASE_TIMEOUT)); + EventClient.DEFAULT_REQUESTED_LEASE_TIME)); conn.close(); server.stop(); diff --git a/jdk/test/javax/management/eventService/ListenerTest.java b/jdk/test/javax/management/eventService/ListenerTest.java index 7195736b662..c4c91135673 100644 --- a/jdk/test/javax/management/eventService/ListenerTest.java +++ b/jdk/test/javax/management/eventService/ListenerTest.java @@ -99,7 +99,7 @@ public class ListenerTest { succeed &= test(new EventClient(ecd, new RMIPushEventRelay(ecd), null, null, - EventClient.DEFAULT_LEASE_TIMEOUT)); + EventClient.DEFAULT_REQUESTED_LEASE_TIME)); conn.close(); server.stop(); diff --git a/jdk/test/javax/management/eventService/NotSerializableNotifTest.java b/jdk/test/javax/management/eventService/NotSerializableNotifTest.java index 0bf0bc5faae..4185b4dd9e4 100644 --- a/jdk/test/javax/management/eventService/NotSerializableNotifTest.java +++ b/jdk/test/javax/management/eventService/NotSerializableNotifTest.java @@ -95,7 +95,7 @@ public class NotSerializableNotifTest { FetchingEventRelay.DEFAULT_MAX_NOTIFICATIONS, null); EventClient ec = new EventClient(ecd, eventRelay, null, null, - EventClient.DEFAULT_LEASE_TIMEOUT); + EventClient.DEFAULT_REQUESTED_LEASE_TIME); // add listener from the client side Listener listener = new Listener(); diff --git a/jdk/test/javax/management/eventService/UsingEventService.java b/jdk/test/javax/management/eventService/UsingEventService.java index 3d768ed8d6f..b46ffe3f6d8 100644 --- a/jdk/test/javax/management/eventService/UsingEventService.java +++ b/jdk/test/javax/management/eventService/UsingEventService.java @@ -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 diff --git a/jdk/test/javax/management/namespace/EventWithNamespaceControlTest.java b/jdk/test/javax/management/namespace/EventWithNamespaceControlTest.java index 1e3901104c7..9bfa9866d85 100644 --- a/jdk/test/javax/management/namespace/EventWithNamespaceControlTest.java +++ b/jdk/test/javax/management/namespace/EventWithNamespaceControlTest.java @@ -85,6 +85,7 @@ public class EventWithNamespaceControlTest extends EventWithNamespaceTest { } } + @Override public Map getServerMap() { Map retValue = Collections.emptyMap(); return retValue; diff --git a/jdk/test/javax/management/namespace/JMXNamespaceSecurityTest.java b/jdk/test/javax/management/namespace/JMXNamespaceSecurityTest.java index 213ffbfb63e..ec201dbda2c 100644 --- a/jdk/test/javax/management/namespace/JMXNamespaceSecurityTest.java +++ b/jdk/test/javax/management/namespace/JMXNamespaceSecurityTest.java @@ -51,6 +51,7 @@ import javax.management.namespace.JMXDomain; import javax.management.namespace.JMXNamespace; import javax.management.namespace.JMXNamespaces; import javax.management.remote.JMXConnectorServer; +import javax.management.ClientContext; /** * diff --git a/jdk/test/javax/management/namespace/JMXNamespaceViewTest.java b/jdk/test/javax/management/namespace/JMXNamespaceViewTest.java index e134968296b..4764f531f6f 100644 --- a/jdk/test/javax/management/namespace/JMXNamespaceViewTest.java +++ b/jdk/test/javax/management/namespace/JMXNamespaceViewTest.java @@ -42,6 +42,7 @@ import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; +import javax.management.ClientContext; import javax.management.JMException; import javax.management.MBeanRegistration; import javax.management.MBeanServer; @@ -62,11 +63,6 @@ import javax.management.remote.JMXServiceURL; */ public class JMXNamespaceViewTest { - // TODO: Remove this when contexts are added. - public static class ClientContext { - public final static String NAMESPACE = "jmx.context"; - } - /** * Describe the configuration of a namespace */ diff --git a/jdk/test/javax/management/namespace/JMXRemoteTargetNamespace.java b/jdk/test/javax/management/namespace/JMXRemoteTargetNamespace.java index 3d83844b1ff..633e9302142 100644 --- a/jdk/test/javax/management/namespace/JMXRemoteTargetNamespace.java +++ b/jdk/test/javax/management/namespace/JMXRemoteTargetNamespace.java @@ -68,13 +68,7 @@ public class JMXRemoteTargetNamespace extends JMXRemoteNamespace { public JMXRemoteTargetNamespace(JMXServiceURL sourceURL, Map optionsMap, String sourceNamespace) { - this(sourceURL,optionsMap,sourceNamespace,false); - } - - public JMXRemoteTargetNamespace(JMXServiceURL sourceURL, - Map optionsMap, String sourceNamespace, - boolean createEventClient) { - super(sourceURL,optionsMap); + super(sourceURL, optionsMap); this.sourceNamespace = sourceNamespace; this.createEventClient = createEventClient(optionsMap); } @@ -92,14 +86,14 @@ public class JMXRemoteTargetNamespace extends JMXRemoteNamespace { } @Override - protected JMXConnector newJMXConnector(JMXServiceURL url, - Map 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; } diff --git a/jdk/test/javax/management/namespace/NamespaceNotificationsTest.java b/jdk/test/javax/management/namespace/NamespaceNotificationsTest.java index 9c5a1a04a1c..0545a36b2ad 100644 --- a/jdk/test/javax/management/namespace/NamespaceNotificationsTest.java +++ b/jdk/test/javax/management/namespace/NamespaceNotificationsTest.java @@ -206,18 +206,10 @@ public class NamespaceNotificationsTest { aconn.addNotificationListener(deep,listener,null,deep); - final JMXServiceURL urlx = new JMXServiceURL(url1.toString()); - System.out.println("conn: "+urlx); - final JMXConnector jc2 = JMXNamespaces.narrowToNamespace( - JMXConnectorFactory.connect(urlx),"server1//server1"); - final JMXConnector jc3 = JMXNamespaces.narrowToNamespace(jc2,"server3"); - jc3.connect(); - System.out.println("JC#3: " + - ((jc3 instanceof JMXAddressable)? - ((JMXAddressable)jc3).getAddress(): - jc3.toString())); - final MBeanServerConnection bconn = - jc3.getMBeanServerConnection(); + MBeanServerConnection iconn = + JMXNamespaces.narrowToNamespace(aconn, "server1//server1"); + MBeanServerConnection bconn = + JMXNamespaces.narrowToNamespace(aconn, "server3"); final ObjectName shallow = new ObjectName("bush:"+ deep.getKeyPropertyListString()); diff --git a/jdk/test/javax/management/namespace/NullDomainObjectNameTest.java b/jdk/test/javax/management/namespace/NullDomainObjectNameTest.java index 3251d388a86..6b1ad51c252 100644 --- a/jdk/test/javax/management/namespace/NullDomainObjectNameTest.java +++ b/jdk/test/javax/management/namespace/NullDomainObjectNameTest.java @@ -155,7 +155,7 @@ public class NullDomainObjectNameTest { // namespace. // RoutingServerProxy proxy = - new RoutingServerProxy(sub, "", "faked", false); + new RoutingServerProxy(sub, "", "faked", true); // These should fail because the ObjectName doesn't start // with "faked//" diff --git a/jdk/test/javax/management/namespace/NullObjectNameTest.java b/jdk/test/javax/management/namespace/NullObjectNameTest.java index 156e7661db5..0e645dc1e3c 100644 --- a/jdk/test/javax/management/namespace/NullObjectNameTest.java +++ b/jdk/test/javax/management/namespace/NullObjectNameTest.java @@ -162,7 +162,7 @@ public class NullObjectNameTest { // this case. // RoutingServerProxy proxy = - new RoutingServerProxy(sub,"","faked",false); + new RoutingServerProxy(sub, "", "faked", true); final ObjectInstance moi3 = proxy.registerMBean(new MyWombat(),null); System.out.println(moi3.getObjectName().toString()+ diff --git a/jdk/test/javax/management/openmbean/CompositeDataStringTest.java b/jdk/test/javax/management/openmbean/CompositeDataStringTest.java index 286bfd1d41b..7a64e52305a 100644 --- a/jdk/test/javax/management/openmbean/CompositeDataStringTest.java +++ b/jdk/test/javax/management/openmbean/CompositeDataStringTest.java @@ -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 { diff --git a/jdk/test/javax/management/remote/mandatory/connectorServer/ForwarderChainTest.java b/jdk/test/javax/management/remote/mandatory/connectorServer/ForwarderChainTest.java index b6c6d87926f..e1dcbcb6e05 100644 --- a/jdk/test/javax/management/remote/mandatory/connectorServer/ForwarderChainTest.java +++ b/jdk/test/javax/management/remote/mandatory/connectorServer/ForwarderChainTest.java @@ -86,16 +86,20 @@ public class ForwarderChainTest { test(cs, mbs); System.out.println("===Remove any leftover forwarders==="); - while (cs.getSystemMBeanServer() instanceof MBeanServerForwarder) { - MBeanServerForwarder mbsf = - (MBeanServerForwarder) cs.getSystemMBeanServer(); - cs.removeMBeanServerForwarder(mbsf); + MBeanServerForwarder systemMBSF = cs.getSystemMBeanServerForwarder(); + // Real code would just do systemMBSF.setMBeanServer(mbs). + while (true) { + MBeanServer xmbs = systemMBSF.getMBeanServer(); + if (!(xmbs instanceof MBeanServerForwarder)) + break; + cs.removeMBeanServerForwarder((MBeanServerForwarder) xmbs); } expectChain(cs, "U", mbs); System.out.println("===Ensure forwarders are called==="); cs.setMBeanServerForwarder(forwarders[0]); - cs.setSystemMBeanServerForwarder(forwarders[1]); + systemMBSF.setMBeanServer(forwarders[1]); + forwarders[1].setMBeanServer(forwarders[0]); expectChain(cs, "1U0", mbs); cs.start(); if (forwarders[0].defaultDomainCount != 0 || @@ -125,8 +129,8 @@ public class ForwarderChainTest { private static void test(JMXConnectorServer cs, MBeanServer end) { // A newly-created connector server might have system forwarders, // so get rid of those. - while (cs.getSystemMBeanServer() != cs.getMBeanServer()) - cs.removeMBeanServerForwarder((MBeanServerForwarder) cs.getSystemMBeanServer()); + MBeanServerForwarder systemMBSF = cs.getSystemMBeanServerForwarder(); + systemMBSF.setMBeanServer(cs.getMBeanServer()); expectChain(cs, "U", end); @@ -139,7 +143,8 @@ public class ForwarderChainTest { expectChain(cs, "U10", end); System.out.println("Add a system forwarder"); - cs.setSystemMBeanServerForwarder(forwarders[2]); + forwarders[2].setMBeanServer(systemMBSF.getMBeanServer()); + systemMBSF.setMBeanServer(forwarders[2]); expectChain(cs, "2U10", end); System.out.println("Add another user forwarder"); @@ -147,7 +152,8 @@ public class ForwarderChainTest { expectChain(cs, "2U310", end); System.out.println("Add another system forwarder"); - cs.setSystemMBeanServerForwarder(forwarders[4]); + forwarders[4].setMBeanServer(systemMBSF.getMBeanServer()); + systemMBSF.setMBeanServer(forwarders[4]); expectChain(cs, "42U310", end); System.out.println("Remove the first user forwarder"); @@ -215,9 +221,8 @@ public class ForwarderChainTest { } case 2: { // add it to the system chain System.out.println("Add " + c + " to system chain"); - if (cs.getSystemMBeanServer() == null) - mbsf.setMBeanServer(null); - cs.setSystemMBeanServerForwarder(mbsf); + mbsf.setMBeanServer(systemMBSF.getMBeanServer()); + systemMBSF.setMBeanServer(mbsf); chain = c + chain; break; } @@ -240,7 +245,7 @@ public class ForwarderChainTest { private static void expectChain( JMXConnectorServer cs, String chain, MBeanServer end) { System.out.println("...expected chain: " + chain); - MBeanServer curr = cs.getSystemMBeanServer(); + MBeanServer curr = cs.getSystemMBeanServerForwarder().getMBeanServer(); int i = 0; while (i < chain.length()) { char c = chain.charAt(i); diff --git a/jdk/test/javax/management/remote/mandatory/connectorServer/StandardForwardersTest.java b/jdk/test/javax/management/remote/mandatory/connectorServer/StandardForwardersTest.java index d2d5e3771ff..25efde9dd0e 100644 --- a/jdk/test/javax/management/remote/mandatory/connectorServer/StandardForwardersTest.java +++ b/jdk/test/javax/management/remote/mandatory/connectorServer/StandardForwardersTest.java @@ -26,6 +26,7 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; +import javax.management.ClientContext; import javax.management.MBeanServer; import javax.management.event.EventClientDelegate; import javax.management.remote.JMXConnectorServer; @@ -62,13 +63,23 @@ public class StandardForwardersTest { public static void main(String[] args) throws Exception { MBeanServer mbs = ManagementFactory.getPlatformMBeanServer(); + MBeanServerForwarder ctxFwd = ClientContext.newContextForwarder(mbs, null); + Forwarder ctx = new Forwarder( + JMXConnectorServer.CONTEXT_FORWARDER, true, ctxFwd.getClass()); + + MBeanServerForwarder locFwd = + ClientContext.newLocalizeMBeanInfoForwarder(mbs); + Forwarder loc = new Forwarder( + JMXConnectorServer.LOCALIZE_MBEAN_INFO_FORWARDER, false, + locFwd.getClass()); + MBeanServerForwarder ecdFwd = - EventClientDelegate.newForwarder(); + EventClientDelegate.newForwarder(mbs, null); Forwarder ecd = new Forwarder( JMXConnectorServer.EVENT_CLIENT_DELEGATE_FORWARDER, true, ecdFwd.getClass()); - Forwarder[] forwarders = {ecd}; + Forwarder[] forwarders = {ctx, loc, ecd}; // Now go through every combination of forwarders. Each forwarder // may be explicitly enabled, explicitly disabled, or left to its @@ -154,9 +165,11 @@ public class StandardForwardersTest { } MBeanServer stop = cs.getMBeanServer(); List> foundClasses = new ArrayList>(); - for (MBeanServer mbs = cs.getSystemMBeanServer(); mbs != stop; - mbs = ((MBeanServerForwarder) mbs).getMBeanServer()) + for (MBeanServer mbs = cs.getSystemMBeanServerForwarder().getMBeanServer(); + mbs != stop; + mbs = ((MBeanServerForwarder) mbs).getMBeanServer()) { foundClasses.add(mbs.getClass()); + } if (!expectedClasses.equals(foundClasses)) { fail("Incorrect forwarder chain: expected " + expectedClasses + "; found " + foundClasses); @@ -165,9 +178,12 @@ public class StandardForwardersTest { // env is consistent if either (a) localizer is not enabled or (b) // localizer is enabled and context is enabled. - // Neither of those is present in this codebase so env is always consistent. private static boolean isConsistent(Map 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) { diff --git a/jdk/test/javax/management/remote/mandatory/provider/ProviderTest.java b/jdk/test/javax/management/remote/mandatory/provider/ProviderTest.java index 0c7e906b6a6..90aa130001d 100644 --- a/jdk/test/javax/management/remote/mandatory/provider/ProviderTest.java +++ b/jdk/test/javax/management/remote/mandatory/provider/ProviderTest.java @@ -46,6 +46,8 @@ import javax.management.MBeanServer; /* * Tests jar services provider are called */ +import provider.JMXConnectorProviderImpl; +import provider.JMXConnectorServerProviderImpl; public class ProviderTest { public static void main(String[] args) throws Exception { System.out.println("Starting ProviderTest"); @@ -56,8 +58,14 @@ public class ProviderTest { dotest(url, mbs); - if(!provider.JMXConnectorProviderImpl.called() || - !provider.JMXConnectorServerProviderImpl.called()) { + boolean clientCalled = provider.JMXConnectorProviderImpl.called(); + boolean serverCalled = provider.JMXConnectorServerProviderImpl.called(); + boolean ok = clientCalled && serverCalled; + if (!ok) { + if (!clientCalled) + System.out.println("Client provider not called"); + if (!serverCalled) + System.out.println("Server provider not called"); System.out.println("Test Failed"); System.exit(1); } diff --git a/jdk/test/javax/management/remote/mandatory/subjectDelegation/SimpleStandard.java b/jdk/test/javax/management/remote/mandatory/subjectDelegation/SimpleStandard.java index 1a0e1dfe66d..d0d75988b1e 100644 --- a/jdk/test/javax/management/remote/mandatory/subjectDelegation/SimpleStandard.java +++ b/jdk/test/javax/management/remote/mandatory/subjectDelegation/SimpleStandard.java @@ -75,7 +75,7 @@ public class SimpleStandard * @return the current value of the "State" attribute. */ public String getState() { - checkSubject(); + checkSubject("getState"); return state; } @@ -85,7 +85,7 @@ public class SimpleStandard * @param s the new value of the "State" attribute. */ public void setState(String s) { - checkSubject(); + checkSubject("setState"); state = s; nbChanges++; } @@ -97,7 +97,7 @@ public class SimpleStandard * @return the current value of the "NbChanges" attribute. */ public int getNbChanges() { - checkSubject(); + checkSubject("getNbChanges"); return nbChanges; } @@ -106,7 +106,7 @@ public class SimpleStandard * attributes of the "SimpleStandard" standard MBean. */ public void reset() { - checkSubject(); + checkSubject("reset"); AttributeChangeNotification acn = new AttributeChangeNotification(this, 0, @@ -149,18 +149,18 @@ public class SimpleStandard * Check that the principal contained in the Subject is of * type JMXPrincipal and refers to the principalName identity. */ - private void checkSubject() { + private void checkSubject(String op) { AccessControlContext acc = AccessController.getContext(); Subject subject = Subject.getSubject(acc); Set principals = subject.getPrincipals(); Principal principal = (Principal) principals.iterator().next(); if (!(principal instanceof JMXPrincipal)) - throw new SecurityException("Authenticated subject contains " + + throw new SecurityException(op+": Authenticated subject contains " + "invalid principal type = " + principal.getClass().getName()); String identity = principal.getName(); if (!identity.equals(principalName)) - throw new SecurityException("Authenticated subject contains " + + throw new SecurityException(op+": Authenticated subject contains " + "invalid principal name = " + identity); }