diff --git a/src/share/classes/com/sun/jmx/defaults/ServiceName.java b/src/share/classes/com/sun/jmx/defaults/ServiceName.java index f70730b8d21dca946fff3c66f4d46c634a9c4b7d..a01ea89e258e4b5113537db70a25d21424e11a08 100644 --- a/src/share/classes/com/sun/jmx/defaults/ServiceName.java +++ b/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/src/share/classes/com/sun/jmx/event/EventParams.java b/src/share/classes/com/sun/jmx/event/EventParams.java index 7d875e1385b727a28f4cb1bec86d553de2f7203f..c0e76634cbd35c87811a82bd8376eb69d55a426f 100644 --- a/src/share/classes/com/sun/jmx/event/EventParams.java +++ b/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/src/share/classes/com/sun/jmx/event/LeaseManager.java b/src/share/classes/com/sun/jmx/event/LeaseManager.java index 09a7a03a42d7fcef53d5cd0ece5983bee963ffc5..2db6fea146bd74df86cbf8d1717dc902393ced11 100644 --- a/src/share/classes/com/sun/jmx/event/LeaseManager.java +++ b/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/src/share/classes/com/sun/jmx/interceptor/SingleMBeanForwarder.java b/src/share/classes/com/sun/jmx/interceptor/SingleMBeanForwarder.java index 4a47d4ce3aafae60874c18aada3f193fda185fa6..cfb40092557f137bf75fad0e3bc3ef62662a5dd6 100644 --- a/src/share/classes/com/sun/jmx/interceptor/SingleMBeanForwarder.java +++ b/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/src/share/classes/com/sun/jmx/mbeanserver/JmxMBeanServer.java b/src/share/classes/com/sun/jmx/mbeanserver/JmxMBeanServer.java index 14ee0ac52791bb6b7d825e0a002cf7265ce711f2..4fc25e912786f5e85b59322f6bae8b98f8302470 100644 --- a/src/share/classes/com/sun/jmx/mbeanserver/JmxMBeanServer.java +++ b/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/src/share/classes/com/sun/jmx/namespace/JMXNamespaceUtils.java b/src/share/classes/com/sun/jmx/namespace/JMXNamespaceUtils.java deleted file mode 100644 index 49f875144f2751604094023018b1c2678ba3dd06..0000000000000000000000000000000000000000 --- a/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/src/share/classes/com/sun/jmx/namespace/ObjectNameRouter.java b/src/share/classes/com/sun/jmx/namespace/ObjectNameRouter.java index 3c7065c478e323ad4f6262e8960424450e17851d..7d668169fc72a90b27d30fe7778463925102b2ea 100644 --- a/src/share/classes/com/sun/jmx/namespace/ObjectNameRouter.java +++ b/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/src/share/classes/com/sun/jmx/namespace/RoutingConnectionProxy.java b/src/share/classes/com/sun/jmx/namespace/RoutingConnectionProxy.java index 443c80f2ae56ad118e4d065d368ff72ac955746e..b09bc84365c48eb9e23af16e6e414ba9008316ee 100644 --- a/src/share/classes/com/sun/jmx/namespace/RoutingConnectionProxy.java +++ b/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/src/share/classes/com/sun/jmx/namespace/RoutingProxy.java b/src/share/classes/com/sun/jmx/namespace/RoutingProxy.java index aa35c5bdaed56754d2591ed463b6632dc3a6f533..12d4b4ceb02c1f4a9788dc764e805ce9125b981e 100644 --- a/src/share/classes/com/sun/jmx/namespace/RoutingProxy.java +++ b/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/src/share/classes/com/sun/jmx/namespace/RoutingServerProxy.java b/src/share/classes/com/sun/jmx/namespace/RoutingServerProxy.java index f58e39816d34d61acf4a83026b81bc3fda1f2296..a11b0eccbf563a072f8ab2204c3dc4a7a0638256 100644 --- a/src/share/classes/com/sun/jmx/namespace/RoutingServerProxy.java +++ b/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/src/share/classes/com/sun/jmx/remote/util/EventClientConnection.java b/src/share/classes/com/sun/jmx/remote/util/EventClientConnection.java index b660898731abf6dc73db56d7244761bc36179780..d02da69c04758221865bc5f83da1f60baeef6dc2 100644 --- a/src/share/classes/com/sun/jmx/remote/util/EventClientConnection.java +++ b/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/src/share/classes/com/sun/net/ssl/internal/www/protocol/https/HttpsURLConnectionOldImpl.java b/src/share/classes/com/sun/net/ssl/internal/www/protocol/https/HttpsURLConnectionOldImpl.java index 8b34a2c3ceb2a4a649c21ac89493fee11abc9b45..0e92d301f2f1625cb2ab52de3c37410320a5d810 100644 --- a/src/share/classes/com/sun/net/ssl/internal/www/protocol/https/HttpsURLConnectionOldImpl.java +++ b/src/share/classes/com/sun/net/ssl/internal/www/protocol/https/HttpsURLConnectionOldImpl.java @@ -497,6 +497,10 @@ public class HttpsURLConnectionOldImpl delegate.setFixedLengthStreamingMode(contentLength); } + public void setFixedLengthStreamingMode(long contentLength) { + delegate.setFixedLengthStreamingMode(contentLength); + } + public void setChunkedStreamingMode (int chunklen) { delegate.setChunkedStreamingMode(chunklen); } diff --git a/src/share/classes/com/sun/security/auth/module/Krb5LoginModule.java b/src/share/classes/com/sun/security/auth/module/Krb5LoginModule.java index 7562f584ada705c22340068d479b83923c751edd..55972eefe89ad667553fc6e38ecd49f00dff7528 100644 --- a/src/share/classes/com/sun/security/auth/module/Krb5LoginModule.java +++ b/src/share/classes/com/sun/security/auth/module/Krb5LoginModule.java @@ -86,6 +86,8 @@ import sun.misc.HexDumpEncoder; * the principal name from the configuration is used. In the * case where the principal property is not set and the principal * entry also does not exist, the user is prompted for the name. + * When this property of entry is set, and useTicketCache + * is set to true, only TGT belonging to this principal is used. * *

The following is a list of configuration options supported * for Krb5LoginModule: @@ -101,18 +103,19 @@ import sun.misc.HexDumpEncoder; * to false if you do not want this module to use the ticket cache. * (Default is False). * This module will - * search for the tickect + * search for the ticket * cache in the following locations: - * For Windows 2000, it will use Local Security Authority (LSA) API - * to get the TGT. On Solaris and Linux + * On Solaris and Linux * it will look for the ticket cache in /tmp/krb5cc_uid * where the uid is numeric user * identifier. If the ticket cache is - * not available in either of the above locations, or if we are on a - * different Windows platform, it will look for the cache as + * not available in the above location, or if we are on a + * Windows platform, it will look for the cache as * {user.home}{file.separator}krb5cc_{user.name}. * You can override the ticket cache location by using - * ticketCache + * ticketCache. + * For Windows, if a ticket cannot be retrieved from the file ticket cache, + * it will use Local Security Authority (LSA) API to get the TGT. *

*

ticketCache:
*
Set this to the name of the ticket @@ -129,20 +132,20 @@ import sun.misc.HexDumpEncoder; *
doNotPrompt:
*
Set this to true if you do not want to be * prompted for the password - * if credentials can - * not be obtained from the cache or keytab.(Default is false) - * If set to true authentication will fail if credentials can - * not be obtained from the cache or keytab.
+ * if credentials can not be obtained from the cache, the keytab, + * or through shared state.(Default is false) + * If set to true, credential must be obtained through cache, keytab, + * or shared state. Otherwise, authentication will fail. *

*

useKeyTab:
*
Set this to true if you * want the module to get the principal's key from the * the keytab.(default value is False) - * If keyatb + * If keytab * is not set then * the module will locate the keytab from the - * Kerberos configuration file.
- * If it is not specifed in the Kerberos configuration file + * Kerberos configuration file. + * If it is not specified in the Kerberos configuration file * then it will look for the file * {user.home}{file.separator}krb5.keytab. *

@@ -210,20 +213,34 @@ import sun.misc.HexDumpEncoder; * exist for the username and password in the shared * state, or if authentication fails. * - * clearPass if, true, this LoginModule clears the - * username and password stored in the module's shared - * state after both phases of authentication - * (login and commit) have completed. + * clearPass if, true, this LoginModule clears the + * username and password stored in the module's shared + * state after both phases of authentication + * (login and commit) have completed. * + *

If the principal system property or key is already provided, the value of + * "javax.security.auth.login.name" in the shared state is ignored. + *

When multiple mechanisms to retrieve a ticket or key is provided, the + * preference order looks like this: + *

    + *
  1. ticket cache + *
  2. keytab + *
  3. shared state + *
  4. user prompt + *
+ *

Note that if any step fails, it will fallback to the next step. + * There's only one exception, it the shared state step fails and + * useFirstPass=true, no user prompt is made. *

Examples of some configuration values for Krb5LoginModule in * JAAS config file and the results are: *

    *

    doNotPrompt=true; *

- *

This is an illegal combination since useTicketCache - * is not set and the user can not be prompted for the password. + *

This is an illegal combination since none of useTicketCache, + * useKeyTab, useFirstPass and tryFirstPass + * is set and the user can not be prompted for the password. *

    - *

    ticketCache = < filename >; + *

    ticketCache = <filename>; *

*

This is an illegal combination since useTicketCache * is not set to true and the ticketCache is set. A configuration error @@ -240,9 +257,9 @@ import sun.misc.HexDumpEncoder; * *

This is an illegal combination since storeKey is set to * true but the key can not be obtained either by prompting the user or from - * the keytab.A configuration error will occur. + * the keytab, or from the shared state. A configuration error will occur. *

    - *

    keyTab = < filename > doNotPrompt=true ; + *

    keyTab = <filename> doNotPrompt=true ; *

*

This is an illegal combination since useKeyTab is not set to true and * the keyTab is set. A configuration error will occur. @@ -260,7 +277,7 @@ import sun.misc.HexDumpEncoder; * with the principal and TGT. If the TGT is not available, * do not prompt the user, instead fail the authentication. *

    - *

    principal=< name >useTicketCache = true + *

    principal=<name>useTicketCache = true * doNotPrompt=true; *

*

Get the TGT from the default cache for the principal and populate the @@ -269,9 +286,9 @@ import sun.misc.HexDumpEncoder; * authentication will fail. *

    *

    useTicketCache = true - * ticketCache=< file name >useKeyTab = true - * keyTab=< keytab filename > - * principal = < principal name > + * ticketCache=<file name>useKeyTab = true + * keyTab=<keytab filename> + * principal = <principal name> * doNotPrompt=true; *

*

Search the cache for the principal's TGT. If it is not available @@ -281,7 +298,7 @@ import sun.misc.HexDumpEncoder; * If the key is not available or valid then authentication will fail. *

    *

    useTicketCache = true - * ticketCache=< file name > + * ticketCache=<file name> *

*

The TGT will be obtained from the cache specified. * The Kerberos principal name used will be the principal name in @@ -292,8 +309,8 @@ import sun.misc.HexDumpEncoder; * The Subject will be populated with the TGT. *

    *

    useKeyTab = true - * keyTab=< keytab filename > - * principal= < principal name > + * keyTab=<keytab filename> + * principal= <principal name> * storeKey=true; *

*

The key for the principal will be retrieved from the keytab. @@ -303,7 +320,7 @@ import sun.misc.HexDumpEncoder; * password entered. *

    *

    useKeyTab = true - * keyTab=< keytabname > + * keyTab=<keytabname> * storeKey=true * doNotPrompt=true; *

@@ -316,21 +333,23 @@ import sun.misc.HexDumpEncoder; * Subject's private credentials set. Otherwise the authentication will * fail. *
    - *

    useKeyTab = true - * keyTab=< file name > storeKey=true - * principal= < principal name > + *

    * useTicketCache=true - * ticketCache=< file name >; + * ticketCache=<file name>; + * useKeyTab = true + * keyTab=<file name> storeKey=true + * principal= <principal name> *

- *

The principal's key will be retrieved from the keytab and added - * to the Subject's private credentials. If the key - * is not available, the - * user will be prompted for the password; the key derived from the password - * will be added to the Subject's private credentials set. The - * client's TGT will be retrieved from the ticket cache and added to the + *

+ * The client's TGT will be retrieved from the ticket cache and added to the * Subject's private credentials. If the TGT is not available - * in the ticket cache, it will be obtained using the authentication + * in the ticket cache, or the TGT's client name does not match the principal + * name, Java will use a secret key to obtain the TGT using the authentication * exchange and added to the Subject's private credentials. + * This secret key will be first retrieved from the keytab. If the key + * is not available, the user will be prompted for the password. In either + * case, the key derived from the password will be added to the + * Subject's private credentials set. *

    *

    isInitiator = false *

@@ -856,11 +875,13 @@ public class Krb5LoginModule implements LoginModule { } private void validateConfiguration() throws LoginException { - if (doNotPrompt && !useTicketCache && !useKeyTab) + if (doNotPrompt && !useTicketCache && !useKeyTab + && !tryFirstPass && !useFirstPass) throw new LoginException ("Configuration Error" + " - either doNotPrompt should be " - + " false or useTicketCache/useKeyTab " + + " false or at least one of useTicketCache, " + + " useKeyTab, tryFirstPass and useFirstPass" + " should be true"); if (ticketCacheName != null && !useTicketCache) throw new LoginException @@ -872,11 +893,12 @@ public class Krb5LoginModule implements LoginModule { throw new LoginException ("Configuration Error - useKeyTab should be set to true " + "to use the keytab" + keyTabName); - if (storeKey && doNotPrompt && !useKeyTab) + if (storeKey && doNotPrompt && !useKeyTab + && !tryFirstPass && !useFirstPass) throw new LoginException - ("Configuration Error - either doNotPrompt " - + "should be set to false or " - + "useKeyTab must be set to true for storeKey option"); + ("Configuration Error - either doNotPrompt should be set to " + + " false or at least one of tryFirstPass, useFirstPass " + + "or useKeyTab must be set to true for storeKey option"); if (renewTGT && !useTicketCache) throw new LoginException ("Configuration Error" diff --git a/src/share/classes/java/net/HttpURLConnection.java b/src/share/classes/java/net/HttpURLConnection.java index eb21c7f3ba468f64560a0d0f9d40d5a5c1041a49..f35c3efa7958741b87ca20a7cd23115f3d3d6b10 100644 --- a/src/share/classes/java/net/HttpURLConnection.java +++ b/src/share/classes/java/net/HttpURLConnection.java @@ -73,10 +73,23 @@ abstract public class HttpURLConnection extends URLConnection { * The fixed content-length when using fixed-length streaming mode. * A value of -1 means fixed-length streaming mode is disabled * for output. + * + *

NOTE: {@link #fixedContentLengthLong} is recommended instead + * of this field, as it allows larger content lengths to be set. + * * @since 1.5 */ protected int fixedContentLength = -1; + /** + * The fixed content-length when using fixed-length streaming mode. + * A value of {@code -1} means fixed-length streaming mode is disabled + * for output. + * + * @since 1.7 + */ + protected long fixedContentLengthLong = -1; + /** * Returns the key for the nth header field. * Some implementations may treat the 0th @@ -109,6 +122,9 @@ abstract public class HttpURLConnection extends URLConnection { * This exception can be queried for the details of the error. *

* This method must be called before the URLConnection is connected. + *

+ * NOTE: {@link #setFixedLengthStreamingMode(long)} is recommended + * instead of this method as it allows larger content lengths to be set. * * @param contentLength The number of bytes which will be written * to the OutputStream. @@ -135,6 +151,52 @@ abstract public class HttpURLConnection extends URLConnection { fixedContentLength = contentLength; } + /** + * This method is used to enable streaming of a HTTP request body + * without internal buffering, when the content length is known in + * advance. + * + *

An exception will be thrown if the application attempts to write + * more data than the indicated content-length, or if the application + * closes the OutputStream before writing the indicated amount. + * + *

When output streaming is enabled, authentication and redirection + * cannot be handled automatically. A {@linkplain HttpRetryException} will + * be thrown when reading the response if authentication or redirection + * are required. This exception can be queried for the details of the + * error. + * + *

This method must be called before the URLConnection is connected. + * + *

The content length set by invoking this method takes precedence + * over any value set by {@link #setFixedLengthStreamingMode(int)}. + * + * @param contentLength + * The number of bytes which will be written to the OutputStream. + * + * @throws IllegalStateException + * if URLConnection is already connected or if a different + * streaming mode is already enabled. + * + * @throws IllegalArgumentException + * if a content length less than zero is specified. + * + * @since 1.7 + */ + public void setFixedLengthStreamingMode(long contentLength) { + if (connected) { + throw new IllegalStateException("Already connected"); + } + if (chunkLength != -1) { + throw new IllegalStateException( + "Chunked encoding streaming mode set"); + } + if (contentLength < 0) { + throw new IllegalArgumentException("invalid content length"); + } + fixedContentLengthLong = contentLength; + } + /* Default chunk size (including chunk header) if not specified; * we want to keep this in sync with the one defined in * sun.net.www.http.ChunkedOutputStream @@ -170,7 +232,7 @@ abstract public class HttpURLConnection extends URLConnection { if (connected) { throw new IllegalStateException ("Can't set streaming mode: already connected"); } - if (fixedContentLength != -1) { + if (fixedContentLength != -1 || fixedContentLengthLong != -1) { throw new IllegalStateException ("Fixed length streaming mode set"); } chunkLength = chunklen <=0? DEFAULT_CHUNK_SIZE : chunklen; diff --git a/src/share/classes/java/security/cert/CertPathValidatorException.java b/src/share/classes/java/security/cert/CertPathValidatorException.java index 8a04aeff5f307238938aba619719c95b54226098..c1ca1d2b8de4cfdb2f41bbd8f58fb01e7a2e14c5 100644 --- a/src/share/classes/java/security/cert/CertPathValidatorException.java +++ b/src/share/classes/java/security/cert/CertPathValidatorException.java @@ -113,7 +113,7 @@ public class CertPathValidatorException extends GeneralSecurityException { * permitted, and indicates that the cause is nonexistent or unknown.) */ public CertPathValidatorException(Throwable cause) { - this(null, cause); + this((cause == null ? null : cause.toString()), cause); } /** diff --git a/src/share/classes/javax/management/AttributeList.java b/src/share/classes/javax/management/AttributeList.java index 1ce3004a337594dfe4c9d5a76f0394bf14a662e7..a629d57ccd4fdaa5c7cc34f759dc0016e51eabeb 100644 --- a/src/share/classes/javax/management/AttributeList.java +++ b/src/share/classes/javax/management/AttributeList.java @@ -1,5 +1,5 @@ /* - * Copyright 1999-2005 Sun Microsystems, Inc. All Rights Reserved. + * Copyright 1999-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 @@ -27,17 +27,23 @@ package javax.management; import java.util.ArrayList; import java.util.Collection; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; /** - * Represents a list of values for attributes of an MBean. The methods - * used for the insertion of {@link javax.management.Attribute - * Attribute} objects in the AttributeList overrides the - * corresponding methods in the superclass - * ArrayList. This is needed in order to insure that the - * objects contained in the AttributeList are only - * Attribute objects. This avoids getting an exception - * when retrieving elements from the AttributeList. + *

Represents a list of values for attributes of an MBean. See the + * {@link MBeanServerConnection#getAttributes getAttributes} and + * {@link MBeanServerConnection#setAttributes setAttributes} methods of + * {@link MBeanServer} and {@link MBeanServerConnection}.

+ * + *

For compatibility reasons, it is possible, though + * highly discouraged, to add objects to an {@code AttributeList} that are + * not instances of {@code Attribute}. However, an {@code AttributeList} + * can be made type-safe, which means that an attempt to add + * an object that is not an {@code Attribute} will produce an {@code + * IllegalArgumentException}. An {@code AttributeList} becomes type-safe + * when the method {@link #asList()} is called on it.

* * @since 1.5 */ @@ -58,8 +64,8 @@ import java.util.List; */ public class AttributeList extends ArrayList { - private transient boolean typeSafe; - private transient boolean tainted; + private transient volatile boolean typeSafe; + private transient volatile boolean tainted; /* Serial version */ private static final long serialVersionUID = -4077085769279709076L; @@ -124,13 +130,63 @@ public class AttributeList extends ArrayList { // Check for non-Attribute objects // - checkTypeSafe(list); + adding(list); // Build the List // super.addAll(list); } + /** + *

Constructs an {@code AttributeList} containing the elements of + * the {@code Map} specified, in the order in which they appear in the + * {@code Map}'s {@link Map#entrySet entrySet}. For each {@code + * key} and {@code value} in the {@code Map}, the constructed + * {@code AttributeList} will contain {@link Attribute#Attribute + * Attribute(key, value)}.

+ * + * @param map the {@code Map} defining the elements of the new + * {@code AttributeList}. + */ + public AttributeList(Map map) { + for (Map.Entry entry : map.entrySet()) + add(new Attribute(entry.getKey(), entry.getValue())); + typeSafe = true; + } + + /** + *

Return a {@code Map} that is a snapshot of the values in this + * {@code AttributeList}. Each key in the {@code Map} is the {@linkplain + * Attribute#getName() name} of an {@code Attribute} in the list, and each + * value is the corresponding {@linkplain Attribute#getValue() value} of + * that {@code Attribute}. The {@code AttributeList} and the {@code Map} + * are unrelated after the call, that is, changes to one do not affect the + * other.

+ * + *

If the {@code AttributeList} contains more than one {@code Attribute} + * with the same name, then the {@code Map} will contain an entry + * for that name where the value is that of the last of those {@code + * Attribute}s.

+ * + * @return the new {@code Map}. + * + * @throws IllegalArgumentException if this {@code AttributeList} contains + * an element that is not an {@code Attribute}. + */ + public Map toMap() { + Map map = new LinkedHashMap(); + + // We can't call adding(this) because we're not necessarily typeSafe + if (tainted) + throw new IllegalArgumentException("AttributeList contains non-Attribute"); + + for (Object x : this) { + Attribute a = (Attribute) x; + map.put(a.getName(), a.getValue()); + } + return map; + } + /** * Return a view of this list as a {@code List}. * Changes to the returned value are reflected by changes @@ -154,11 +210,9 @@ public class AttributeList extends ArrayList { */ @SuppressWarnings("unchecked") public List asList() { - if (!typeSafe) { - if (tainted) - checkTypeSafe(this); - typeSafe = true; - } + typeSafe = true; + if (tainted) + adding((Collection) this); // will throw IllegalArgumentException return (List) (List) this; } @@ -175,7 +229,7 @@ public class AttributeList extends ArrayList { * Inserts the attribute specified as an element at the position specified. * Elements with an index greater than or equal to the current position are * shifted up. If the index is out of range (index < 0 || index > - * size() a RuntimeOperationsException should be raised, wrapping the + * size()) a RuntimeOperationsException should be raised, wrapping the * java.lang.IndexOutOfBoundsException thrown. * * @param object The Attribute object to be inserted. @@ -245,8 +299,7 @@ public class AttributeList extends ArrayList { public boolean addAll(int index, AttributeList list) { try { return super.addAll(index, list); - } - catch (IndexOutOfBoundsException e) { + } catch (IndexOutOfBoundsException e) { throw new RuntimeOperationsException(e, "The specified index is out of range"); } @@ -258,96 +311,77 @@ public class AttributeList extends ArrayList { * been called on this instance. */ + /** + * {@inheritDoc} + * @throws IllegalArgumentException if this {@code AttributeList} is + * type-safe and {@code element} is not an + * {@code Attribute}. + */ @Override - public boolean add(Object o) { - if (!tainted) - tainted = isTainted(o); - if (typeSafe) - checkTypeSafe(o); - return super.add(o); + public boolean add(Object element) { + adding(element); + return super.add(element); } + /** + * {@inheritDoc} + * @throws IllegalArgumentException if this {@code AttributeList} is + * type-safe and {@code element} is not an + * {@code Attribute}. + */ @Override public void add(int index, Object element) { - if (!tainted) - tainted = isTainted(element); - if (typeSafe) - checkTypeSafe(element); + adding(element); super.add(index, element); } + /** + * {@inheritDoc} + * @throws IllegalArgumentException if this {@code AttributeList} is + * type-safe and {@code c} contains an + * element that is not an {@code Attribute}. + */ @Override public boolean addAll(Collection c) { - if (!tainted) - tainted = isTainted(c); - if (typeSafe) - checkTypeSafe(c); + adding(c); return super.addAll(c); } + /** + * {@inheritDoc} + * @throws IllegalArgumentException if this {@code AttributeList} is + * type-safe and {@code c} contains an + * element that is not an {@code Attribute}. + */ @Override public boolean addAll(int index, Collection c) { - if (!tainted) - tainted = isTainted(c); - if (typeSafe) - checkTypeSafe(c); + adding(c); return super.addAll(index, c); } + /** + * {@inheritDoc} + * @throws IllegalArgumentException if this {@code AttributeList} is + * type-safe and {@code element} is not an + * {@code Attribute}. + */ @Override public Object set(int index, Object element) { - if (!tainted) - tainted = isTainted(element); - if (typeSafe) - checkTypeSafe(element); + adding(element); return super.set(index, element); } - /** - * IllegalArgumentException if o is a non-Attribute object. - */ - private static void checkTypeSafe(Object o) { - try { - o = (Attribute) o; - } catch (ClassCastException e) { - throw new IllegalArgumentException(e); - } - } - - /** - * IllegalArgumentException if c contains any non-Attribute objects. - */ - private static void checkTypeSafe(Collection c) { - try { - Attribute a; - for (Object o : c) - a = (Attribute) o; - } catch (ClassCastException e) { - throw new IllegalArgumentException(e); - } - } - - /** - * Returns true if o is a non-Attribute object. - */ - private static boolean isTainted(Object o) { - try { - checkTypeSafe(o); - } catch (IllegalArgumentException e) { - return true; - } - return false; + private void adding(Object x) { + if (x == null || x instanceof Attribute) + return; + if (typeSafe) + throw new IllegalArgumentException("Not an Attribute: " + x); + else + tainted = true; } - /** - * Returns true if c contains any non-Attribute objects. - */ - private static boolean isTainted(Collection c) { - try { - checkTypeSafe(c); - } catch (IllegalArgumentException e) { - return true; - } - return false; + private void adding(Collection c) { + for (Object x : c) + adding(x); } } diff --git a/src/share/classes/javax/management/ClientContext.java b/src/share/classes/javax/management/ClientContext.java new file mode 100644 index 0000000000000000000000000000000000000000..bddde7ca64cc63df66e260042b0dcac75b49a0f2 --- /dev/null +++ b/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/src/share/classes/javax/management/Descriptor.java b/src/share/classes/javax/management/Descriptor.java index a74fb11cde70c5174c24b00890d8ceeeabbc6da1..b9c98f4068be3ec9203f619af61ebd01aa0dfacf 100644 --- a/src/share/classes/javax/management/Descriptor.java +++ b/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/src/share/classes/javax/management/JMX.java b/src/share/classes/javax/management/JMX.java index ac7468d07dd9a1ac72abbcd4273dc546b793c4d8..60b9605b3152f891cb2425537415e544b011462a 100644 --- a/src/share/classes/javax/management/JMX.java +++ b/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);
      * 
* @@ -808,4 +830,80 @@ public class JMX { ((DynamicWrapperMBean) mbean).getWrappedObject() : mbean; return (MBeanInjector.injectsSendNotification(resource)); } + + /** + *

Return the version of the JMX specification that a (possibly remote) + * MBean Server is using. The JMX specification described in this + * documentation is version 2.0. The earlier versions that might be + * reported by this method are 1.0, 1.1, 1.2, and 1.4. (There is no 1.3.) + * All of these versions and all future versions can be compared using + * {@link String#compareTo(String)}. So, for example, to tell if + * {@code mbsc} is running at least version 2.0 you can write:

+ * + *
+     * String version = JMX.getSpecificationVersion(mbsc, null);
+     * boolean atLeast2dot0 = (version.compareTo("2.0") >= 0);
+     * 
+ * + *

A remote MBean Server might be running an earlier version of the + * JMX API, and in that case certain + * features might not be available in it.

+ * + *

The version of the MBean Server {@code mbsc} is not necessarily + * the version of all namespaces within that MBean Server, for example + * if some of them use {@link javax.management.namespace.JMXRemoteNamespace + * JMXRemoteNamespace}. To determine the version of the namespace + * that a particular MBean is in, give its name as the {@code mbeanName} + * parameter.

+ * + * @param mbsc a connection to an MBean Server. + * + * @param mbeanName the name of an MBean within that MBean Server, or null. + * If non-null, the namespace of this name, as determined by + * {@link JMXNamespaces#getContainingNamespace + * JMXNamespaces.getContainingNamespace}, is the one whose specification + * version will be returned. + * + * @return the JMX specification version reported by that MBean Server. + * + * @throws IllegalArgumentException if {@code mbsc} is null, or if + * {@code mbeanName} includes a wildcard character ({@code *} or {@code ?}) + * in its namespace. + * + * @throws IOException if the version cannot be obtained, either because + * there is a communication problem or because the remote MBean Server + * does not have the appropriate {@linkplain + * MBeanServerDelegateMBean#getSpecificationVersion() attribute}. + * + * @see Interoperability between + * versions of the JMX specification + * @see MBeanServerDelegateMBean#getSpecificationVersion + */ + public static String getSpecificationVersion( + MBeanServerConnection mbsc, ObjectName mbeanName) + throws IOException { + if (mbsc == null) + throw new IllegalArgumentException("Null MBeanServerConnection"); + + String namespace; + if (mbeanName == null) + namespace = ""; + else + namespace = JMXNamespaces.getContainingNamespace(mbeanName); + if (namespace.contains("*") || namespace.contains("?")) { + throw new IllegalArgumentException( + "ObjectName contains namespace wildcard: " + mbeanName); + } + + try { + if (namespace.length() > 0) + mbsc = JMXNamespaces.narrowToNamespace(mbsc, namespace); + return (String) mbsc.getAttribute( + MBeanServerDelegate.DELEGATE_NAME, "SpecificationVersion"); + } catch (IOException e) { + throw e; + } catch (Exception e) { + throw new IOException(e); + } + } } diff --git a/src/share/classes/javax/management/MBeanInfo.java b/src/share/classes/javax/management/MBeanInfo.java index 3561e3cfc132d7ebd212eb67f01cfb8ac289fbd2..49973c66ce67d285182c5c57a48d9620673b9ab2 100644 --- a/src/share/classes/javax/management/MBeanInfo.java +++ b/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/src/share/classes/javax/management/MBeanServerConnection.java b/src/share/classes/javax/management/MBeanServerConnection.java index 16ce58bb883a8e3c7cc83d77d73357e1fdfa7b92..fadebc10730bf724a55f4ab745a15c46b24a4e80 100644 --- a/src/share/classes/javax/management/MBeanServerConnection.java +++ b/src/share/classes/javax/management/MBeanServerConnection.java @@ -532,8 +532,30 @@ public interface MBeanServerConnection extends NotificationManager { /** - * Enables the values of several attributes of a named MBean. The MBean - * is identified by its object name. + *

Retrieves the values of several attributes of a named MBean. The MBean + * is identified by its object name.

+ * + *

If one or more attributes cannot be retrieved for some reason, they + * will be omitted from the returned {@code AttributeList}. The caller + * should check that the list is the same size as the {@code attributes} + * array. To discover what problem prevented a given attribute from being + * retrieved, call {@link #getAttribute getAttribute} for that attribute.

+ * + *

Here is an example of calling this method and checking that it + * succeeded in retrieving all the requested attributes:

+ * + *
+     * String[] attrNames = ...;
+     * AttributeList list = mbeanServerConnection.getAttributes(objectName, attrNames);
+     * if (list.size() == attrNames.length)
+     *     System.out.println("All attributes were retrieved successfully");
+     * else {
+     *     {@code List} missing = new {@code ArrayList}({@link java.util.Arrays#asList Arrays.asList}(attrNames));
+     *     missing.removeAll(list.toMap().keySet());
+     *     System.out.println("Did not retrieve: " + missing);
+     * }
+     * 
* * @param name The object name of the MBean from which the * attributes are retrieved. @@ -557,6 +579,7 @@ public interface MBeanServerConnection extends NotificationManager { throws InstanceNotFoundException, ReflectionException, IOException; + /** * Sets the value of a specific attribute of a named MBean. The MBean * is identified by its object name. @@ -592,10 +615,36 @@ public interface MBeanServerConnection extends NotificationManager { ReflectionException, IOException; - /** - * Sets the values of several attributes of a named MBean. The MBean is - * identified by its object name. + *

Sets the values of several attributes of a named MBean. The MBean is + * identified by its object name.

+ * + *

If one or more attributes cannot be set for some reason, they will be + * omitted from the returned {@code AttributeList}. The caller should check + * that the input {@code AttributeList} is the same size as the output one. + * To discover what problem prevented a given attribute from being retrieved, + * it will usually be possible to call {@link #setAttribute setAttribute} + * for that attribute, although this is not guaranteed to work. (For + * example, the values of two attributes may have been rejected because + * they were inconsistent with each other. Setting one of them alone might + * be allowed.)

+ * + *

Here is an example of calling this method and checking that it + * succeeded in setting all the requested attributes:

+ * + *
+     * AttributeList inputAttrs = ...;
+     * AttributeList outputAttrs = mbeanServerConnection.setAttributes(objectName, inputAttrs);
+     * if (inputAttrs.size() == outputAttrs.size())
+     *     System.out.println("All attributes were set successfully");
+     * else {
+     *     {@code List} missing = new {@code ArrayList}(inputAttrs.toMap().keySet());
+     *     missing.removeAll(outputAttrs.toMap().keySet());
+     *     System.out.println("Did not set: " + missing);
+     * }
+     * 
* * @param name The object name of the MBean within which the * attributes are to be set. @@ -622,7 +671,39 @@ public interface MBeanServerConnection extends NotificationManager { throws InstanceNotFoundException, ReflectionException, IOException; /** - * Invokes an operation on an MBean. + *

Invokes an operation on an MBean.

+ * + *

Because of the need for a {@code signature} to differentiate + * possibly-overloaded operations, it is much simpler to invoke operations + * through an {@linkplain JMX#newMBeanProxy(MBeanServerConnection, ObjectName, + * Class) MBean proxy} where possible. For example, suppose you have a + * Standard MBean interface like this:

+ * + *
+     * public interface FooMBean {
+     *     public int countMatches(String[] patterns, boolean ignoreCase);
+     * }
+     * 
+ * + *

The {@code countMatches} operation can be invoked as follows:

+ * + *
+     * String[] myPatterns = ...;
+     * int count = (Integer) mbeanServerConnection.invoke(
+     *         objectName,
+     *         "countMatches",
+     *         new Object[] {myPatterns, true},
+     *         new String[] {String[].class.getName(), boolean.class.getName()});
+     * 
+ * + *

Alternatively, it can be invoked through a proxy as follows:

+ * + *
+     * String[] myPatterns = ...;
+     * FooMBean fooProxy = JMX.newMBeanProxy(
+     *         mbeanServerConnection, objectName, FooMBean.class);
+     * int count = fooProxy.countMatches(myPatterns, true);
+     * 
* * @param name The object name of the MBean on which the method is * to be invoked. @@ -630,7 +711,8 @@ public interface MBeanServerConnection extends NotificationManager { * @param params An array containing the parameters to be set when * the operation is invoked * @param signature An array containing the signature of the - * operation. The class objects will be loaded using the same + * operation, an array of class names in the format returned by + * {@link Class#getName()}. The class objects will be loaded using the same * class loader as the one used for loading the MBean on which the * operation was invoked. * diff --git a/src/share/classes/javax/management/MBeanServerNotification.java b/src/share/classes/javax/management/MBeanServerNotification.java index 723d2d4c1e679371e78ac9df09d85017cc1c88f0..d19c73a555a84342e33450d9c41a36884adf62a9 100644 --- a/src/share/classes/javax/management/MBeanServerNotification.java +++ b/src/share/classes/javax/management/MBeanServerNotification.java @@ -27,15 +27,70 @@ 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);
+ * 
+ * + *

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/src/share/classes/javax/management/Notification.java b/src/share/classes/javax/management/Notification.java index 07fc19b3e364c0ba2c5ed21f74856794456a30c6..de07ec447488f547dfd1519e77897de24deed7db 100644 --- a/src/share/classes/javax/management/Notification.java +++ b/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/src/share/classes/javax/management/QueryNotificationFilter.java b/src/share/classes/javax/management/QueryNotificationFilter.java index 7d1990fa2b9e1d703f57428aad47d37cd91fdb11..5d7e7815b80af5fd83b4136eb38b26753be272f1 100644 --- a/src/share/classes/javax/management/QueryNotificationFilter.java +++ b/src/share/classes/javax/management/QueryNotificationFilter.java @@ -26,7 +26,6 @@ package javax.management; import com.sun.jmx.mbeanserver.NotificationMBeanSupport; -import com.sun.jmx.mbeanserver.Util; import java.lang.reflect.InvocationHandler; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; @@ -43,6 +42,11 @@ import java.util.Set; * on both the client and the server in the remote case, so using this class * instead is recommended where possible.

* + *

Because this class was introduced in version 2.0 of the JMX API, + * it may not be present on a remote JMX agent that is running an earlier + * version. The method {@link JMX#getSpecificationVersion + * JMX.getSpecificationVersion} can be used to determine the remote version.

+ * *

This class uses the {@linkplain Query Query API} to specify the * filtering logic. For example, to select only notifications where the * {@linkplain Notification#getType() type} is {@code "com.example.mytype"}, diff --git a/src/share/classes/javax/management/event/EventClient.java b/src/share/classes/javax/management/event/EventClient.java index 96c4017e450e2345f16d858b24d4f19152ba9251..a2f6bc617fee8f904eff4c1d2936e80141f54925 100644 --- a/src/share/classes/javax/management/event/EventClient.java +++ b/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/src/share/classes/javax/management/event/EventClientDelegate.java b/src/share/classes/javax/management/event/EventClientDelegate.java index 13329ea9e6f0da22070887b8e5d91f58a2c88c4c..8eeeeb1f5c12d1a64fe4bf04b9d7b8f00962ac3b 100644 --- a/src/share/classes/javax/management/event/EventClientDelegate.java +++ b/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/src/share/classes/javax/management/event/EventClientDelegateMBean.java b/src/share/classes/javax/management/event/EventClientDelegateMBean.java index a9718a931dca6ae6e98bfa7a007515a720cf3eff..e6030b613a96d24bb3e2de3dbecced5728f056dc 100644 --- a/src/share/classes/javax/management/event/EventClientDelegateMBean.java +++ b/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/src/share/classes/javax/management/event/EventRelay.java b/src/share/classes/javax/management/event/EventRelay.java index d723bb04c4e535864a595a311c46aa5c00f8795b..d106a1605513cbf91146f9a6724b0f99ca50fd4a 100644 --- a/src/share/classes/javax/management/event/EventRelay.java +++ b/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/src/share/classes/javax/management/event/package-info.java b/src/share/classes/javax/management/event/package-info.java index c4c7dbed63dd1ae7e9cb302ebd4d96771bc1ad2f..728b095139e339b61358197e6a961a6de0b42c49 100644 --- a/src/share/classes/javax/management/event/package-info.java +++ b/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/src/share/classes/javax/management/namespace/JMXNamespaces.java b/src/share/classes/javax/management/namespace/JMXNamespaces.java index 429a9d466d6175a2f56e0f5b4d7c74d2b7fb8211..f19dfa570e4955ef10c5735ca13073265e63ed24 100644 --- a/src/share/classes/javax/management/namespace/JMXNamespaces.java +++ b/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/src/share/classes/javax/management/namespace/JMXRemoteNamespace.java b/src/share/classes/javax/management/namespace/JMXRemoteNamespace.java index 6958f57f2d72db7a17c99e2acb93fb058c33b1f3..1c4d29bae30cb0ae80acb68a14ae2bfa1eef8b46 100644 --- a/src/share/classes/javax/management/namespace/JMXRemoteNamespace.java +++ b/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: + *

    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 options map that will be passed to the + * {@link JMXConnectorFactory} when {@linkplain + * JMXConnectorFactory#newJMXConnector creating} the + * {@link JMXConnector} that can connect to the remote source + * MBean Server. + * @return A JMXConnector to use to connect to the remote server + * @throws IOException if the connector could not be created. + * @see JMXConnectorFactory#newJMXConnector(javax.management.remote.JMXServiceURL, java.util.Map) + * @see #JMXRemoteNamespace + */ + protected JMXConnector newJMXConnector(JMXServiceURL url, + Map optionsMap) throws IOException { + 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,options);
    +     *     private final String subnamespace;
    +     *
    +     *     JMXRemoteSubNamespace(
    +     *             JMXServiceURL url, Map{@code } env, String subnamespace) {
    +     *        super(url, env);
          *        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)};
    -     *    }
    +     *     }
    +     *
    +     *     {@code @Override}
    +     *     protected MBeanServerConnection getMBeanServerConnection(
    +     *             JMXConnector jmxc) throws IOException {
    +     *         MBeanServerConnection mbsc = super.getMBeanServerConnection(jmxc);
    +     *         return {@link JMXNamespaces#narrowToNamespace(MBeanServerConnection,String)
    +     *                 JMXNamespaces.narrowToNamespace(mbsc, 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: - *

    + * + *

    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 {
    -     *    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)};
    -     *    }
    +     *     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}: only configuring - * the client side (this object) is not enough.
    - * In summary, this technique should be used if the remote server + * javax.management.event.EventClientDelegateMBean}: configuring only + * the client side (this object) is not enough.

    + * + *

    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 url The JMXServiceURL of the remote server. - * @param optionsMap An unmodifiable options map that will be passed to the - * {@link JMXConnectorFactory} when {@linkplain - * JMXConnectorFactory#newJMXConnector creating} the - * {@link JMXConnector} that can connect to the remote source - * MBean Server. - * @return An unconnected JMXConnector to use to connect to the remote - * server - * @throws java.io.IOException if the connector could not be created. - * @see JMXConnectorFactory#newJMXConnector(javax.management.remote.JMXServiceURL, java.util.Map) - * @see #JMXRemoteNamespace + * specification).

    + * + * @param jmxc the newly-created {@code JMXConnector}. + * + * @return an {@code MBeanServerConnection} connected to the remote + * MBeanServer. + * + * @throws IOException if the connection cannot be made. If this method + * throws {@code IOException} then the calling {@link #connect()} method + * will also fail with an {@code IOException}. + * + * @see #connect */ - protected JMXConnector newJMXConnector(JMXServiceURL url, - Map optionsMap) throws IOException { - final JMXConnector c = - JMXConnectorFactory.newJMXConnector(jmxURL, optionsMap); -// TODO: uncomment this when contexts are added -// return ClientContext.withDynamicContext(c); - return c; + protected MBeanServerConnection getMBeanServerConnection(JMXConnector jmxc) + throws IOException { + final MBeanServerConnection mbsc = jmxc.getMBeanServerConnection(); + try { + return ClientContext.withDynamicContext(mbsc); + } catch (IllegalArgumentException e) { + LOG.log(Level.FINER, "ClientContext.withDynamicContext", e); + return mbsc; + } } + /** + * {@inheritDoc} + * + *

    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/src/share/classes/javax/management/openmbean/CompositeDataSupport.java b/src/share/classes/javax/management/openmbean/CompositeDataSupport.java index 4ef93b47611ccc649afec6294b6c993607a171d2..bd7c77af9b4542296b369e52a6bbf6f6a14dae98 100644 --- a/src/share/classes/javax/management/openmbean/CompositeDataSupport.java +++ b/src/share/classes/javax/management/openmbean/CompositeDataSupport.java @@ -33,12 +33,14 @@ import java.io.Serializable; import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.LinkedHashMap; import java.util.Map; import java.util.Set; import java.util.SortedMap; import java.util.TreeMap; // jmx import +import java.util.TreeSet; // @@ -60,16 +62,15 @@ public class CompositeDataSupport * respective values. * A {@link SortedMap} is used for faster retrieval of elements. */ - private SortedMap contents = new TreeMap(); + private final SortedMap contents; /** * @serial The composite type of this composite data instance. */ - private CompositeType compositeType; + private final CompositeType compositeType; /** - *

    - * Constructs a CompositeDataSupport instance with the specified + *

    Constructs a CompositeDataSupport instance with the specified * compositeType, whose item values * are specified by itemValues[], in the same order as in * itemNames[]. @@ -79,103 +80,67 @@ public class CompositeDataSupport * The items contained in this CompositeDataSupport instance are * internally stored in a TreeMap, * thus sorted in ascending lexicographic order of their names, for faster - * retrieval of individual item values. - *

    - * The constructor checks that all the constraints listed below for each + * retrieval of individual item values.

    + * + *

    The constructor checks that all the constraints listed below for each * parameter are satisfied, - * and throws the appropriate exception if they are not. - *

    - * @param compositeType the composite type of this composite - * data instance; - * must not be null. - *

    - * @param itemNames itemNames must list, in any order, all the - * item names defined in compositeType; - * the order in which the names are listed, is used to - * match values in itemValues[]; - * must not be null or empty. - *

    - * @param itemValues the values of the items, listed in the same order as - * their respective names in itemNames; - * each item value can be null, but if it is non-null it must be - * a valid value for the open type defined in compositeType for the corresponding item; - * must be of the same size as itemNames; must not be null or empty. - *

    - * @throws IllegalArgumentException compositeType is null, or itemNames[] or itemValues[] is null or empty, - * or one of the elements in itemNames[] is a null or empty string, - * or itemNames[] and itemValues[] are not of the same size. - *

    - * @throws OpenDataException itemNames[] or itemValues[]'s size differs from - * the number of items defined in compositeType, - * or one of the elements in itemNames[] does not exist as an item name defined in compositeType, - * or one of the elements in itemValues[] is not a valid value for the corresponding item - * as defined in compositeType. - *

    + * and throws the appropriate exception if they are not.

    + * + * @param compositeType the composite type of this composite + * data instance; must not be null. + * + * @param itemNames itemNames must list, in any order, all the + * item names defined in compositeType; the order in which the + * names are listed, is used to match values in itemValues[]; must + * not be null. + * + * @param itemValues the values of the items, listed in the same order as + * their respective names in itemNames; each item value can be + * null, but if it is non-null it must be a valid value for the open type + * defined in compositeType for the corresponding item; must be of + * the same size as itemNames; must not be null. + * + * @throws IllegalArgumentException compositeType is null, or + * itemNames[] or itemValues[] is null or empty, or one + * of the elements in itemNames[] is a null or empty string, or + * itemNames[] and itemValues[] are not of the same size. + * + * @throws OpenDataException itemNames[] or + * itemValues[]'s size differs from the number of items defined in + * compositeType, or one of the elements in itemNames[] + * does not exist as an item name defined in compositeType, or one + * of the elements in itemValues[] is not a valid value for the + * corresponding item as defined in compositeType. */ - public CompositeDataSupport(CompositeType compositeType, String[] itemNames, Object[] itemValues) - throws OpenDataException { - - // Check compositeType is not null - // - if (compositeType == null) { - throw new IllegalArgumentException("Argument compositeType cannot be null."); - } - - // item names defined in compositeType: - Set namesSet = compositeType.keySet(); - - // Check the array itemNames is not null or empty (length!=0) and - // that there is no null element or empty string in it - // - checkForNullElement(itemNames, "itemNames"); - checkForEmptyString(itemNames, "itemNames"); + public CompositeDataSupport( + CompositeType compositeType, String[] itemNames, Object[] itemValues) + throws OpenDataException { + this(makeMap(itemNames, itemValues), compositeType); + } - // Check the array itemValues is not null or empty (length!=0) - // (NOTE: we allow null values as array elements) - // - if ( (itemValues == null) || (itemValues.length == 0) ) { - throw new IllegalArgumentException("Argument itemValues[] cannot be null or empty."); - } + private static SortedMap makeMap( + String[] itemNames, Object[] itemValues) + throws OpenDataException { - // Check that the sizes of the 2 arrays itemNames and itemValues are the same - // + if (itemNames == null || itemValues == null) + throw new IllegalArgumentException("Null itemNames or itemValues"); if (itemNames.length != itemValues.length) { - throw new IllegalArgumentException("Array arguments itemNames[] and itemValues[] "+ - "should be of same length (got "+ itemNames.length + - " and "+ itemValues.length +")."); - } - - // Check the size of the 2 arrays is equal to the number of items defined in compositeType - // - if (itemNames.length != namesSet.size()) { - throw new OpenDataException("The size of array arguments itemNames[] and itemValues[] should be equal to the number of items defined"+ - " in argument compositeType (found "+ itemNames.length +" elements in itemNames[] and itemValues[],"+ - " expecting "+ namesSet.size() +" elements according to compositeType."); - } - - // Check parameter itemNames[] contains all names defined in the compositeType of this instance - // - if ( ! Arrays.asList(itemNames).containsAll(namesSet) ) { - throw new OpenDataException("Argument itemNames[] does not contain all names defined in the compositeType of this instance."); + throw new IllegalArgumentException( + "Different lengths: itemNames[" + itemNames.length + + "], itemValues[" + itemValues.length + "]"); } - // Check each element of itemValues[], if not null, is of the open type defined for the corresponding item - // - OpenType itemType; - for (int i=0; i map = new TreeMap(); + for (int i = 0; i < itemNames.length; i++) { + String name = itemNames[i]; + if (name == null || name.equals("")) + throw new IllegalArgumentException("Null or empty item name"); + if (map.containsKey(name)) + throw new OpenDataException("Duplicate item name " + name); + map.put(itemNames[i], itemValues[i]); } - // Initialize internal fields: compositeType and contents - // - this.compositeType = compositeType; - for (int i=0; iitems. * This constructor converts the keys to a string array and the values to an object array and calls * CompositeDataSupport(javax.management.openmbean.CompositeType, java.lang.String[], java.lang.Object[]). - *

    + * * @param compositeType the composite type of this composite data instance; * must not be null. - *

    * @param items the mappings of all the item names to their values; * items must contain all the item names defined in compositeType; - * must not be null or empty. - *

    - * @throws IllegalArgumentException compositeType is null, or items is null or empty, - * or one of the keys in items is a null or empty string, - * or one of the values in items is null. - *

    - * @throws OpenDataException items' size differs from the number of items defined in compositeType, - * or one of the keys in items does not exist as an item name defined in compositeType, - * or one of the values in items is not a valid value for the corresponding item - * as defined in compositeType. - *

    - * @throws ArrayStoreException one or more keys in items is not of the class java.lang.String. - *

    + * must not be null. + * + * @throws IllegalArgumentException compositeType is null, or + * items is null, or one of the keys in items is a null + * or empty string. + * @throws OpenDataException items' size differs from the + * number of items defined in compositeType, or one of the + * keys in items does not exist as an item name defined in + * compositeType, or one of the values in items + * is not a valid value for the corresponding item as defined in + * compositeType. + * @throws ArrayStoreException one or more keys in items is not of + * the class java.lang.String. + * + * @see #toMap */ public CompositeDataSupport(CompositeType compositeType, Map items) throws OpenDataException { + this(makeMap(items), compositeType); + } - - // Let the other constructor do the job, as the call to another constructor must be the first call - // - this( compositeType, - (items==null ? null : items.keySet().toArray(new String[items.size()])), // may raise an ArrayStoreException - (items==null ? null : items.values().toArray()) ); + private static SortedMap makeMap(Map items) { + if (items == null) + throw new IllegalArgumentException("Null items map"); + if (items.containsKey(null) || items.containsKey("")) + throw new IllegalArgumentException("Null or empty item name"); + + SortedMap map = new TreeMap(); + for (Object key : items.keySet()) { + if (!(key instanceof String)) { + throw new ArrayStoreException("Item name is not string: " + key); + // This can happen because of erasure. The particular + // exception is a historical artifact - an implementation + // detail that leaked into the API. + } + map.put((String) key, items.get(key)); + } + return map; } - /** - * - */ - private static void checkForNullElement(Object[] arg, String argName) { - if ( (arg == null) || (arg.length == 0) ) { - throw new IllegalArgumentException( - "Argument "+ argName +"[] cannot be null or empty."); + private CompositeDataSupport( + SortedMap items, CompositeType compositeType) + throws OpenDataException { + + // Check compositeType is not null + // + if (compositeType == null) { + throw new IllegalArgumentException("Argument compositeType cannot be null."); } - for (int i=0; i namesFromType = compositeType.keySet(); + Set namesFromItems = items.keySet(); + + // This is just a comparison, but we do it this way for a better + // exception message. + if (!namesFromType.equals(namesFromItems)) { + Set extraFromType = new TreeSet(namesFromType); + extraFromType.removeAll(namesFromItems); + Set extraFromItems = new TreeSet(namesFromItems); + extraFromItems.removeAll(namesFromType); + if (!extraFromType.isEmpty() || !extraFromItems.isEmpty()) { + throw new OpenDataException( + "Item names do not match CompositeType: " + + "names in items but not in CompositeType: " + extraFromItems + + "; names in CompositeType but not in items: " + extraFromType); } } - } - /** - * - */ - private static void checkForEmptyString(String[] arg, String argName) { - for (int i=0; i itemType = compositeType.getType(name); + if (!itemType.isValue(value)) { + throw new OpenDataException( + "Argument value of wrong type for item " + name + + ": value " + value + ", type " + itemType); + } } } + + // Initialize internal fields: compositeType and contents + // + this.compositeType = compositeType; + this.contents = items; } /** @@ -328,6 +328,54 @@ public class CompositeDataSupport return Collections.unmodifiableCollection(contents.values()); } + /** + *

    Returns a Map representing the contents of the given CompositeData. + * Each item in the CompositeData is represented by an entry in the map, + * where the name and value of the item are the key and value of the entry. + * The returned value is modifiable but modifications to it have no effect + * on the original CompositeData.

    + * + *

    For example, if you have a CompositeData {@code cd1} and you want + * to produce another CompositeData {@code cd2} which is the same except + * that the value of its {@code id} item has been changed to 253, you + * could write:

    + * + *
    +     * CompositeData cd1 = ...;
    +     * {@code Map} map = CompositeDataSupport.toMap(cd1);
    +     * assert(map.get("id") instanceof Integer);
    +     * map.put("id", 253);
    +     * CompositeData cd2 = {@link #CompositeDataSupport(CompositeType, Map)
    +     * new CompositeDataSupport}(cd1.getCompositeType(), map);
    +     * 
    + * + *

    Logically, this method would be a method in the {@link CompositeData} + * interface, but cannot be for compatibility reasons.

    + * + * @param cd the CompositeData to convert to a Map. + * + * @return a Map that is a copy of the contents of {@code cd}. + * + * @throws IllegalArgumentException if {@code cd} is null. + * + * @see #CompositeDataSupport(CompositeType, Map) + */ + public static Map toMap(CompositeData cd) { + if (cd == null) + throw new IllegalArgumentException("Null argument"); + + // If we really wanted, we could check whether cd is a + // CompositeDataSupport and return a copy of cd.contents if so, + // but I don't think that would be substantially faster. + Map map = new LinkedHashMap(); + CompositeType ct = cd.getCompositeType(); + for (String key : ct.keySet()) { + Object value = cd.get(key); + map.put(key, value); + } + return map; + } + /** * Compares the specified obj parameter with this * CompositeDataSupport instance for equality. diff --git a/src/share/classes/javax/management/package.html b/src/share/classes/javax/management/package.html index 14027d648ea40993dbfd6e59e1f36f4e50941d03..372c30efd9f26216e82a3f6dd2b6b19456c867f6 100644 --- a/src/share/classes/javax/management/package.html +++ b/src/share/classes/javax/management/package.html @@ -1,7 +1,7 @@ - -javax.management package - - - -

    Provides the core classes for the Java Management Extensions.

    + --> + + +

    Provides the core classes for the Java Management Extensions.

    -

    The Java Management Extensions - (JMXTM) API is a standard - API for management and monitoring. Typical uses include:

    +

    The Java Management Extensions + (JMXTM) API is a standard + API for management and monitoring. Typical uses include:

    -
      -
    • consulting and changing application configuration
    • +
        +
      • consulting and changing application configuration
      • -
      • accumulating statistics about application behavior and - making them available
      • +
      • accumulating statistics about application behavior and + making them available
      • -
      • notifying of state changes and erroneous conditions.
      • -
      +
    • notifying of state changes and erroneous conditions.
    • +
    -

    The JMX API can also be used as part of a solution for - managing systems, networks, and so on.

    +

    The JMX API can also be used as part of a solution for + managing systems, networks, and so on.

    -

    The API includes remote access, so a remote management - program can interact with a running application for these - purposes.

    +

    The API includes remote access, so a remote management + program can interact with a running application for these + purposes.

    -

    MBeans

    +

    MBeans

    -

    The fundamental notion of the JMX API is the MBean. - An MBean is a named managed object representing a - resource. It has a management interface consisting - of:

    +

    The fundamental notion of the JMX API is the MBean. + An MBean is a named managed object representing a + resource. It has a management interface consisting + of:

    • named and typed attributes that can be read and/or @@ -92,40 +92,40 @@ have any questions.
           public interface ConfigurationMBean {
      -	public int getCacheSize();
      -	public void setCacheSize(int size);
      -	public long getLastChangedTime();
      -	public void save();
      +         public int getCacheSize();
      +         public void setCacheSize(int size);
      +         public long getLastChangedTime();
      +         public void save();
           }
      -      
      + -

      The methods getCacheSize and - setCacheSize define a read-write attribute of - type int called CacheSize (with an - initial capital, unlike the JavaBeans convention).

      +

      The methods getCacheSize and + setCacheSize define a read-write attribute of + type int called CacheSize (with an + initial capital, unlike the JavaBeans convention).

      -

      The method getLastChangedTime defines an - attribute of type long called - LastChangedTime. This is a read-only attribute, - since there is no method setLastChangedTime.

      +

      The method getLastChangedTime defines an + attribute of type long called + LastChangedTime. This is a read-only attribute, + since there is no method setLastChangedTime.

      -

      The method save defines an operation called - save. It is not an attribute, since its name - does not begin with get, set, or - is.

      +

      The method save defines an operation called + save. It is not an attribute, since its name + does not begin with get, set, or + is.

      -

      The exact naming patterns for Standard MBeans are detailed in - the JMX Specification.

      +

      The exact naming patterns for Standard MBeans are detailed in + the JMX Specification.

      -

      There are two ways to make a Java object that is an MBean - with this management interface. One is for the object to be - of a class that has exactly the same name as the Java - interface but without the MBean suffix. So in - the example the object would be of the class - Configuration, in the same Java package as - ConfigurationMBean. The second way is to use the - {@link javax.management.StandardMBean StandardMBean} - class.

      +

      There are two ways to make a Java object that is an MBean + with this management interface. One is for the object to be + of a class that has exactly the same name as the Java + interface but without the MBean suffix. So in + the example the object would be of the class + Configuration, in the same Java package as + ConfigurationMBean. The second way is to use the + {@link javax.management.StandardMBean StandardMBean} + class.

      Defining Standard MBeans with annotations

      @@ -272,37 +272,37 @@ have any questions.
           int cacheSize = mbs.getAttribute(name, "CacheSize");
           {@link javax.management.Attribute Attribute} newCacheSize =
      -    	new Attribute("CacheSize", new Integer(2000));
      +         new Attribute("CacheSize", new Integer(2000));
           mbs.setAttribute(name, newCacheSize);
           mbs.invoke(name, "save", new Object[0], new Class[0]);
      -      
      +

      Alternatively, if you have a Java interface that corresponds to the management interface for the MBean, you can use an MBean proxy like this:

      -
      +        
           ConfigurationMBean conf =
               {@link javax.management.JMX#newMBeanProxy
                   JMX.newMBeanProxy}(mbs, name, ConfigurationMBean.class);
           int cacheSize = conf.getCacheSize();
           conf.setCacheSize(2000);
           conf.save();
      -      
      +
      -

      Using an MBean proxy is just a convenience. The second - example ends up calling the same MBeanServer - operations as the first one.

      +

      Using an MBean proxy is just a convenience. The second + example ends up calling the same MBeanServer + operations as the first one.

      -

      An MBean Server can be queried for MBeans whose names match - certain patterns and/or whose attributes meet certain - constraints. Name patterns are constructed using the {@link - javax.management.ObjectName ObjectName} class and constraints - are constructed using the {@link javax.management.Query Query} - class. The methods {@link - javax.management.MBeanServer#queryNames queryNames} and {@link - javax.management.MBeanServer#queryMBeans queryMBeans} then - perform the query.

      +

      An MBean Server can be queried for MBeans whose names match + certain patterns and/or whose attributes meet certain + constraints. Name patterns are constructed using the {@link + javax.management.ObjectName ObjectName} class and constraints + are constructed using the {@link javax.management.Query Query} + class. The methods {@link + javax.management.MBeanServer#queryNames queryNames} and {@link + javax.management.MBeanServer#queryMBeans queryMBeans} then + perform the query.

      MBean lifecycle and resource injection

      @@ -407,6 +407,92 @@ have any questions. So for example an SNMP GET operation might result in a getAttribute on the MBean Server.

      +

      Interoperability between versions of the JMX + specification

      + +

      When a client connects to a server using the JMX Remote + API, it is possible that they do not have the same version + of the JMX specification. The version of the JMX + specification described here is version 2.0. Previous + versions were 1.0, 1.1, 1.2, and 1.4. (There was no 1.3.) + The standard JMX Remote API is defined to work with version + 1.2 onwards, so in standards-based deployment the only + interoperability questions that arise concern version 1.2 + onwards.

      + +

      Every version of the JMX specification continues to + implement the features of previous versions. So when the + client is running an earlier version than the server, there + should not be any interoperability concerns. The only + exception is the unlikely one where a pre-2.0 client used + the string {@code //} in the domain part of an {@link + javax.management.ObjectName ObjectName}.

      + +

      When the client is running a later version than the server, + certain newer features may not be available, as detailed in + the next sections. The method {@link + javax.management.JMX#getSpecificationVersion + JMX.getSpecificationVersion} can be used to determine the + server version to check if required features are + available.

      + +

      If the remote MBean Server is 1.4

      + +
        + +
      • You cannot use {@link + javax.management.QueryNotificationFilter + QueryNotificationFilter} in {@link + javax.management.MBeanServerConnection#addNotificationListener + addNotificationListener} since this class did not exist + in 1.4.

        + +
      • In an attribute in a query, you cannot access values + inside complex types using dot syntax, for example + {@link javax.management.Query#attr Query.attr}{@code + ("HeapMemoryUsage.used")}.

        + +
      • The packages {@link javax.management.event} and + {@link javax.management.namespace} did not exist in 1.4, + so you cannot remotely create instances of the MBeans + they define.

        + +
      • Even if the remote MBean Server is 2.0, you cannot in + general suppose that {@link + javax.management.event.EventClient EventClient} or + {@link javax.management.ClientContext ClientContext} + will work there without first checking. If the remote + MBean Server is 1.4 then those checks will return false. + An attempt to use these features without checking will + fail in the same way as for a remote 2.0 that is not + configured to support them.

        +
      + +

      If the remote MBean Server is 1.2

      + +

      In addition to the above,

      + +
        + +
      • You cannot use wildcards in a key property of an + {@link javax.management.ObjectName ObjectName}, for + example {@code domain:type=Foo,name=*}. Wildcards that + match whole properties are still allowed, for example + {@code *:*} or {@code *:type=Foo,*}.

        + +
      • You cannot use {@link + javax.management.Query#isInstanceOf Query.isInstanceOf} + in a query.

        + +
      • You cannot use dot syntax such as {@code + HeapMemoryUsage.used} in the {@linkplain + javax.management.monitor.Monitor#setObservedAttribute + observed attribute} of a monitor, as described in the + documentation for the {@link javax.management.monitor} + package.

        + +
      +

      @see Java SE 6 Platform documentation on JMX technology diff --git a/src/share/classes/javax/management/remote/JMXConnectorFactory.java b/src/share/classes/javax/management/remote/JMXConnectorFactory.java index 051d8b34c45cffeaca0f17aa462934410e03d0b2..77f700c32159a79ad619be8cd622f262c3e462a1 100644 --- a/src/share/classes/javax/management/remote/JMXConnectorFactory.java +++ b/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/src/share/classes/javax/management/remote/JMXConnectorServer.java b/src/share/classes/javax/management/remote/JMXConnectorServer.java index 88348ff5de39d5517f7c4a3409f11c2ce7261937..c9b4721911bd65bcff741b01ab710bd67e78cba6 100644 --- a/src/share/classes/javax/management/remote/JMXConnectorServer.java +++ b/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.

      - * - *

      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.

      + * 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 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 - */ - 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() + * @throws UnsupportedOperationException if {@link + * #supportsSystemMBeanServerForwarder} returns false. + * + * @see #supportsSystemMBeanServerForwarder + * @see #setMBeanServerForwarder + * + * @since 1.7 */ - 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. - * - * 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. + * 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. 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/src/share/classes/javax/management/remote/JMXConnectorServerMBean.java b/src/share/classes/javax/management/remote/JMXConnectorServerMBean.java index fb6f883a2b74f63bd110ea59b1ce24fe8d7196ac..b784b87e48c84c83c8b612c7bb61ce41751ddac6 100644 --- a/src/share/classes/javax/management/remote/JMXConnectorServerMBean.java +++ b/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/src/share/classes/javax/management/remote/rmi/RMIConnector.java b/src/share/classes/javax/management/remote/rmi/RMIConnector.java index 1f17143b9486a70bcdbfba079181d8fdb6945f3b..5f1b8ac71ce6b10fc39e8d1d3770ec44aa13161f 100644 --- a/src/share/classes/javax/management/remote/rmi/RMIConnector.java +++ b/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; - } - - private void popDefaultClassLoader(final ClassLoader old) { - AccessController.doPrivileged(new PrivilegedAction() { public Void run() { - Thread.currentThread().setContextClassLoader(old); + 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; + } + }); } //-------------------------------------------------------------------- diff --git a/src/share/classes/javax/management/remote/rmi/RMIConnectorServer.java b/src/share/classes/javax/management/remote/rmi/RMIConnectorServer.java index e56020b472223609231c9b947df94184ca9a9759..9d38c7e89443323e67f219b023bd2adf524b90c9 100644 --- a/src/share/classes/javax/management/remote/rmi/RMIConnectorServer.java +++ b/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/src/share/classes/sun/net/www/protocol/http/HttpURLConnection.java b/src/share/classes/sun/net/www/protocol/http/HttpURLConnection.java index 5aec18973aa7fc5a0d34d9fcbd6ce8dfb5f6f02b..6f3250b30eb3fb755adf30fbfcdd875c447cd30a 100644 --- a/src/share/classes/sun/net/www/protocol/http/HttpURLConnection.java +++ b/src/share/classes/sun/net/www/protocol/http/HttpURLConnection.java @@ -435,8 +435,14 @@ public class HttpURLConnection extends java.net.HttpURLConnection { if (streaming()) { if (chunkLength != -1) { requests.set ("Transfer-Encoding", "chunked"); - } else { - requests.set ("Content-Length", String.valueOf(fixedContentLength)); + } else { /* fixed content length */ + if (fixedContentLengthLong != -1) { + requests.set ("Content-Length", + String.valueOf(fixedContentLengthLong)); + } else if (fixedContentLength != -1) { + requests.set ("Content-Length", + String.valueOf(fixedContentLength)); + } } } else if (poster != null) { /* add Content-Length & POST/PUT data */ @@ -871,11 +877,17 @@ public class HttpURLConnection extends java.net.HttpURLConnection { ps = (PrintStream)http.getOutputStream(); if (streaming()) { if (strOutputStream == null) { - if (fixedContentLength != -1) { - strOutputStream = new StreamingOutputStream (ps, fixedContentLength); - } else if (chunkLength != -1) { - strOutputStream = - new StreamingOutputStream (new ChunkedOutputStream (ps, chunkLength), -1); + if (chunkLength != -1) { /* chunked */ + strOutputStream = new StreamingOutputStream( + new ChunkedOutputStream(ps, chunkLength), -1L); + } else { /* must be fixed content length */ + long length = 0L; + if (fixedContentLengthLong != -1) { + length = fixedContentLengthLong; + } else if (fixedContentLength != -1) { + length = fixedContentLength; + } + strOutputStream = new StreamingOutputStream(ps, length); } } return strOutputStream; @@ -895,7 +907,8 @@ public class HttpURLConnection extends java.net.HttpURLConnection { } private boolean streaming () { - return (fixedContentLength != -1) || (chunkLength != -1); + return (fixedContentLength != -1) || (fixedContentLengthLong != -1) || + (chunkLength != -1); } /* @@ -2619,8 +2632,8 @@ public class HttpURLConnection extends java.net.HttpURLConnection { class StreamingOutputStream extends FilterOutputStream { - int expected; - int written; + long expected; + long written; boolean closed; boolean error; IOException errorExcp; @@ -2631,10 +2644,10 @@ public class HttpURLConnection extends java.net.HttpURLConnection { * In the 2nd case, we make sure the expected number of * of bytes are actually written */ - StreamingOutputStream (OutputStream os, int expectedLength) { + StreamingOutputStream (OutputStream os, long expectedLength) { super (os); expected = expectedLength; - written = 0; + written = 0L; closed = false; error = false; } @@ -2643,7 +2656,7 @@ public class HttpURLConnection extends java.net.HttpURLConnection { public void write (int b) throws IOException { checkError(); written ++; - if (expected != -1 && written > expected) { + if (expected != -1L && written > expected) { throw new IOException ("too many bytes written"); } out.write (b); @@ -2658,7 +2671,7 @@ public class HttpURLConnection extends java.net.HttpURLConnection { public void write (byte[] b, int off, int len) throws IOException { checkError(); written += len; - if (expected != -1 && written > expected) { + if (expected != -1L && written > expected) { out.close (); throw new IOException ("too many bytes written"); } @@ -2691,7 +2704,7 @@ public class HttpURLConnection extends java.net.HttpURLConnection { return; } closed = true; - if (expected != -1) { + if (expected != -1L) { /* not chunked */ if (written != expected) { error = true; diff --git a/src/share/classes/sun/net/www/protocol/https/HttpsURLConnectionImpl.java b/src/share/classes/sun/net/www/protocol/https/HttpsURLConnectionImpl.java index 55b1859ba15eb05ea17fdf537ca64b418e4c87ac..c2a37012822b2a4d43849d48ea04e65ecc59e4e0 100644 --- a/src/share/classes/sun/net/www/protocol/https/HttpsURLConnectionImpl.java +++ b/src/share/classes/sun/net/www/protocol/https/HttpsURLConnectionImpl.java @@ -527,6 +527,10 @@ public class HttpsURLConnectionImpl delegate.setFixedLengthStreamingMode(contentLength); } + public void setFixedLengthStreamingMode(long contentLength) { + delegate.setFixedLengthStreamingMode(contentLength); + } + public void setChunkedStreamingMode (int chunklen) { delegate.setChunkedStreamingMode(chunklen); } diff --git a/src/share/classes/sun/security/jgss/GSSContextImpl.java b/src/share/classes/sun/security/jgss/GSSContextImpl.java index a506394703bd9996ebc2cc0f5a1d8353a28bc527..046f6478277b108d1405ff7025a1602e8804dac2 100644 --- a/src/share/classes/sun/security/jgss/GSSContextImpl.java +++ b/src/share/classes/sun/security/jgss/GSSContextImpl.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2006 Sun Microsystems, Inc. All Rights Reserved. + * Copyright 2000-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 @@ -284,7 +284,8 @@ class GSSContextImpl implements GSSContext { ByteArrayOutputStream bos = new ByteArrayOutputStream(100); acceptSecContext(new ByteArrayInputStream(inTok, offset, len), bos); - return bos.toByteArray(); + byte[] out = bos.toByteArray(); + return (out.length == 0) ? null : out; } public void acceptSecContext(InputStream inStream, diff --git a/src/share/classes/sun/security/jgss/spnego/SpNegoContext.java b/src/share/classes/sun/security/jgss/spnego/SpNegoContext.java index 5ea5cd36aeae95c48984a93edc464002738d363e..7185b0e08a720a7ce66fd045111a1094f50e5d66 100644 --- a/src/share/classes/sun/security/jgss/spnego/SpNegoContext.java +++ b/src/share/classes/sun/security/jgss/spnego/SpNegoContext.java @@ -1,5 +1,5 @@ /* - * Copyright 2005-2006 Sun Microsystems, Inc. All Rights Reserved. + * Copyright 2005-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 @@ -413,13 +413,14 @@ public class SpNegoContext implements GSSContextSpi { // pull out the mechanism token byte[] accept_token = targToken.getResponseToken(); if (accept_token == null) { - // return wth failure - throw new GSSException(errorCode, -1, - "mechansim token from server is null"); + if (!isMechContextEstablished()) { + // return with failure + throw new GSSException(errorCode, -1, + "mechanism token from server is null"); + } + } else { + mechToken = GSS_initSecContext(accept_token); } - - mechToken = GSS_initSecContext(accept_token); - // verify MIC if (!GSSUtil.useMSInterop()) { byte[] micToken = targToken.getMechListMIC(); @@ -428,7 +429,6 @@ public class SpNegoContext implements GSSContextSpi { "verification of MIC on MechList Failed!"); } } - if (isMechContextEstablished()) { state = STATE_DONE; retVal = mechToken; @@ -556,9 +556,6 @@ public class SpNegoContext implements GSSContextSpi { // get the token for mechanism byte[] accept_token = GSS_acceptSecContext(mechToken); - if (accept_token == null) { - valid = false; - } // verify MIC if (!GSSUtil.useMSInterop() && valid) { diff --git a/src/share/classes/sun/security/provider/certpath/OCSPResponse.java b/src/share/classes/sun/security/provider/certpath/OCSPResponse.java index 24d88374c1c6c0573a7ff91c8da361c9bbe45fa2..e9afb29f5e523caf8c752ae8820d42b29393f6ab 100644 --- a/src/share/classes/sun/security/provider/certpath/OCSPResponse.java +++ b/src/share/classes/sun/security/provider/certpath/OCSPResponse.java @@ -151,6 +151,10 @@ class OCSPResponse { private SingleResponse singleResponse; + // Maximum clock skew in milliseconds (10 minutes) allowed when checking + // validity of OCSP responses + private static final long MAX_CLOCK_SKEW = 600000; + // an array of all of the CRLReasons (used in SingleResponse) private static CRLReason[] values = CRLReason.values(); @@ -583,7 +587,9 @@ class OCSPResponse { } } - Date now = new Date(); + long now = System.currentTimeMillis(); + Date nowPlusSkew = new Date(now + MAX_CLOCK_SKEW); + Date nowMinusSkew = new Date(now - MAX_CLOCK_SKEW); if (DEBUG != null) { String until = ""; if (nextUpdate != null) { @@ -593,8 +599,8 @@ class OCSPResponse { thisUpdate + until); } // Check that the test date is within the validity interval - if ((thisUpdate != null && now.before(thisUpdate)) || - (nextUpdate != null && now.after(nextUpdate))) { + if ((thisUpdate != null && nowPlusSkew.before(thisUpdate)) || + (nextUpdate != null && nowMinusSkew.after(nextUpdate))) { if (DEBUG != null) { DEBUG.println("Response is unreliable: its validity " + diff --git a/src/share/native/java/util/zip/zip_util.c b/src/share/native/java/util/zip/zip_util.c index 5d518cf4ced4f75620e790df2da6f72216ae86fb..3b00b9500ac65bc202c87353fb9df0a15ef6efeb 100644 --- a/src/share/native/java/util/zip/zip_util.c +++ b/src/share/native/java/util/zip/zip_util.c @@ -273,8 +273,8 @@ static const jlong END_MAXLEN = 0xFFFF + ENDHDR; /* * Searches for end of central directory (END) header. The contents of * the END header will be read and placed in endbuf. Returns the file - * position of the END header, otherwise returns 0 if the END header - * was not found or -1 if an error occurred. + * position of the END header, otherwise returns -1 if the END header + * was not found or an error occurred. */ static jlong findEND(jzfile *zip, void *endbuf) @@ -314,7 +314,7 @@ findEND(jzfile *zip, void *endbuf) } } } - return 0; /* END header not found */ + return -1; /* END header not found */ } /* @@ -460,9 +460,8 @@ if (1) { zip->msg = message; goto Catch; } else ((void)0) /* * Reads zip file central directory. Returns the file position of first - * CEN header, otherwise returns 0 if central directory not found or -1 - * if an error occurred. If zip->msg != NULL then the error was a zip - * format error and zip->msg has the error text. + * CEN header, otherwise returns -1 if an error occured. If zip->msg != NULL + * then the error was a zip format error and zip->msg has the error text. * Always pass in -1 for knownTotal; it's used for a recursive call. */ static jlong @@ -488,9 +487,9 @@ readCEN(jzfile *zip, jint knownTotal) /* Get position of END header */ if ((endpos = findEND(zip, endbuf)) == -1) - return -1; /* system error */ + return -1; /* no END header or system error */ - if (endpos == 0) return 0; /* END header not found */ + if (endpos == 0) return 0; /* only END header present */ freeCEN(zip); diff --git a/test/com/sun/net/httpserver/bugs/FixedLengthInputStream.java b/test/com/sun/net/httpserver/bugs/FixedLengthInputStream.java index 76a71fc120bc2f0edfd9c94d12ed0f6ad9df47d1..d5cdfdf253315ad28b8ec790009ecfa55256b5ad 100644 --- a/test/com/sun/net/httpserver/bugs/FixedLengthInputStream.java +++ b/test/com/sun/net/httpserver/bugs/FixedLengthInputStream.java @@ -23,7 +23,7 @@ /** * @test - * @bug 6756771 + * @bug 6756771 6755625 * @summary com.sun.net.httpserver.HttpServer should handle POSTs larger than 2Gig */ @@ -44,34 +44,16 @@ public class FixedLengthInputStream { static final long POST_SIZE = 4L * 1024L * 1024L * 1024L; // 4Gig - /* Remove when CR 6755625 is fixed */ - static final String requestHeaders = ((new StringBuilder()) - .append("POST /flis/ HTTP/1.1\r\n") - .append("User-Agent: Java/1.7.0\r\n") - .append("Host: localhost\r\n") - .append("Accept: text/html, image/gif, image/jpeg,") - .append( " *; q=.2, */*; q=.2\r\n") - .append("Content-Length: 4294967296\r\n\r\n")).toString(); - void test(String[] args) throws IOException { HttpServer httpServer = startHttpServer(); int port = httpServer.getAddress().getPort(); try { - /* Uncomment & when CR 6755625 is fixed, remove socket code URL url = new URL("http://localhost:" + port + "/flis/"); HttpURLConnection uc = (HttpURLConnection)url.openConnection(); uc.setDoOutput(true); uc.setRequestMethod("POST"); uc.setFixedLengthStreamingMode(POST_SIZE); OutputStream os = uc.getOutputStream(); - */ - - Socket socket = new Socket("localhost", port); - OutputStream os = socket.getOutputStream(); - PrintStream ps = new PrintStream(os); - debug("Request: " + requestHeaders); - ps.print(requestHeaders); - ps.flush(); /* create a 32K byte array with data to POST */ int thirtyTwoK = 32 * 1024; @@ -84,18 +66,12 @@ public class FixedLengthInputStream os.write(ba); } - /* Uncomment & when CR 6755625 is fixed, remove socket code os.close(); InputStream is = uc.getInputStream(); while(is.read(ba) != -1); is.close(); - */ - - InputStream is = socket.getInputStream(); - is.read(); - socket.close(); - pass(); + pass(); } finally { httpServer.stop(0); } diff --git a/test/java/security/cert/CertPathValidatorException/GetMessage.java b/test/java/security/cert/CertPathValidatorException/GetMessage.java new file mode 100644 index 0000000000000000000000000000000000000000..dd51af7e42cddfdfdaa0348298e3b5c9ef4e8570 --- /dev/null +++ b/test/java/security/cert/CertPathValidatorException/GetMessage.java @@ -0,0 +1,63 @@ +/* + * 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 6765046 + * @summary check that getMessage(cause) returns cause.toString if cause != null + */ + +import java.security.cert.CertPathValidatorException; + +public class GetMessage { + private static volatile boolean failed = false; + public static void main(String[] args) throws Exception { + + Throwable[] causes = { + new Throwable(), + new Throwable("message"), + new Throwable("message", new Throwable()) }; + + for (Throwable cause: causes) { + CertPathValidatorException cpve = + new CertPathValidatorException(cause); + + // from CertPathValidatorException(Throwable cause) spec: + // The detail message is set to (cause==null ? null : cause.toString() ) + // (which typically contains the class and detail message of cause). + String expMsg = (cause == null ? null : cause.toString()); + String actualMsg = cpve.getMessage(); + + boolean msgsEqual = + (expMsg == null ? actualMsg == null : expMsg.equals(actualMsg)); + if (!msgsEqual) { + System.out.println("expected message:" + expMsg); + System.out.println("getMessage():" + actualMsg); + failed = true; + } + } + if (failed) { + throw new Exception("Some tests FAILED"); + } + } +} diff --git a/test/java/util/zip/TestEmptyZip.java b/test/java/util/zip/TestEmptyZip.java index d19dee4d4469d4c233a36a066570fd4ce6b95319..f4ad29b60f2f9630415a0e8c090bb2012e589b04 100644 --- a/test/java/util/zip/TestEmptyZip.java +++ b/test/java/util/zip/TestEmptyZip.java @@ -39,35 +39,24 @@ public class TestEmptyZip { throw new Exception("failed to delete " + zipName); } - // Verify 0-length file cannot be read f.createNewFile(); - ZipFile zf = null; try { - zf = new ZipFile(f); - fail(); - } catch (Exception ex) { - check(ex.getMessage().contains("zip file is empty")); - } finally { - if (zf != null) { - zf.close(); + // Verify 0-length file cannot be read + checkCannotRead(f); + + // Verify non-zip file cannot be read + OutputStream out = new FileOutputStream(f); + try { + out.write("class Foo { }".getBytes()); + } finally { + out.close(); } - } + checkCannotRead(f); - ZipInputStream zis = null; - try { - zis = new ZipInputStream(new FileInputStream(f)); - ZipEntry ze = zis.getNextEntry(); - check(ze == null); - } catch (Exception ex) { - unexpected(ex); } finally { - if (zis != null) { - zis.close(); - } + f.delete(); } - f.delete(); - // Verify 0-entries file can be written write(f); @@ -78,6 +67,29 @@ public class TestEmptyZip { f.delete(); } + static void checkCannotRead(File f) throws IOException { + try { + new ZipFile(f).close(); + fail(); + } catch (ZipException ze) { + if (f.length() == 0) { + check(ze.getMessage().contains("zip file is empty")); + } else { + pass(); + } + } + ZipInputStream zis = null; + try { + zis = new ZipInputStream(new FileInputStream(f)); + ZipEntry ze = zis.getNextEntry(); + check(ze == null); + } catch (IOException ex) { + unexpected(ex); + } finally { + if (zis != null) zis.close(); + } + } + static void write(File f) throws Exception { ZipOutputStream zos = null; try { diff --git a/test/javax/management/Introspector/AnnotationTest.java b/test/javax/management/Introspector/AnnotationTest.java index e6d3c10b0427955beacd21661c2ac09cab12c9ac..49ca0d3405c95ff78d01e070aaf414596e26b0ed 100644 --- a/test/javax/management/Introspector/AnnotationTest.java +++ b/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/test/javax/management/MBeanServer/AttributeListMapTest.java b/test/javax/management/MBeanServer/AttributeListMapTest.java new file mode 100644 index 0000000000000000000000000000000000000000..94dd39306a3c64aa9c01dc305cc08f76c0d9d2b1 --- /dev/null +++ b/test/javax/management/MBeanServer/AttributeListMapTest.java @@ -0,0 +1,115 @@ +/* + * 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 6336968 + * @summary Test AttributeList.toMap + * @author Eamonn McManus + */ + +import java.math.BigInteger; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import javax.management.Attribute; +import javax.management.AttributeList; + +public class AttributeListMapTest { + + private static String failure; + + public static void main(String[] args) throws Exception { + AttributeList attrs = new AttributeList(Arrays.asList( + new Attribute("Str", "Five"), + new Attribute("Int", 5), + new Attribute("Flt", 5.0))); + + Map map = attrs.toMap(); + final Map expectMap = new HashMap(); + for (Attribute attr : attrs.asList()) + expectMap.put(attr.getName(), attr.getValue()); + assertEquals("Initial map", expectMap, map); + assertEquals("Initial map size", 3, map.size()); + assertEquals("Name set", expectMap.keySet(), map.keySet()); + assertEquals("Values", new HashSet(expectMap.values()), + new HashSet(map.values())); + assertEquals("Entry set", expectMap.entrySet(), map.entrySet()); + + AttributeList attrs2 = new AttributeList(map); + assertEquals("AttributeList from Map", attrs, attrs2); + // This assumes that the Map conserves the order of the attributes, + // which is not specified but true because we use LinkedHashMap. + + // Check that toMap fails if the list contains non-Attribute elements. + AttributeList attrs3 = new AttributeList(attrs); + attrs3.add("Hello"); // allowed but curious + try { + map = attrs3.toMap(); + fail("toMap succeeded on list with non-Attribute elements"); + } catch (Exception e) { + assertEquals("Exception for toMap with non-Atttribute elements", + IllegalArgumentException.class, e.getClass()); + } + + // Check that the Map does not reflect changes made to the list after + // the Map was obtained. + AttributeList attrs4 = new AttributeList(attrs); + map = attrs4.toMap(); + attrs4.add(new Attribute("Big", new BigInteger("5"))); + assertEquals("Map after adding element to list", expectMap, map); + + // Check that if there is more than one Attribute with the same name + // then toMap() chooses the last of them. + AttributeList attrs5 = new AttributeList(attrs); + attrs5.add(new Attribute("Str", "Cinq")); + map = attrs5.toMap(); + assertEquals("Size of Map for list with duplicate attribute name", + 3, map.size()); + Object value = map.get("Str"); + assertEquals("Value of Str in Map for list with two values for it", + "Cinq", value); + + if (failure == null) + System.out.println("TEST PASSED"); + else + throw new Exception("TEST FAILED: " + failure); + } + + private static void assertEquals(String what, Object expect, Object actual) { + if (eq(expect, actual)) + System.out.println("OK: " + what); + else + fail(what + ": expected " + expect + ", got " + actual); + } + + private static boolean eq(Object x, Object y) { + return (x == null) ? (y == null) : x.equals(y); + } + + private static void fail(String why) { + System.out.println("FAIL: " + why); + failure = why; + } +} diff --git a/test/javax/management/MBeanServer/AttributeListTypeSafeTest.java b/test/javax/management/MBeanServer/AttributeListTypeSafeTest.java new file mode 100644 index 0000000000000000000000000000000000000000..ed2fc96fd06159c10f449e141e6a890b94d0de31 --- /dev/null +++ b/test/javax/management/MBeanServer/AttributeListTypeSafeTest.java @@ -0,0 +1,109 @@ +/* + * 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 6336968 + * @summary Test adding non-Attribute values to an AttributeList. + * @author Eamonn McManus + */ + +import java.util.Collections; +import java.util.List; +import javax.management.Attribute; +import javax.management.AttributeList; + +public class AttributeListTypeSafeTest { + + private static String failure; + + public static void main(String[] args) throws Exception { + // Test calling asList after adding non-Attribute by various means + for (Op op : Op.values()) { + AttributeList alist = new AttributeList(); + alist.add(new Attribute("foo", "bar")); + doOp(alist, op); + String what = "asList() after calling " + op + " with non-Attribute"; + try { + List lista = alist.asList(); + fail(what + ": succeeded but should not have"); + } catch (IllegalArgumentException e) { + System.out.println("OK: " + what + ": got IllegalArgumentException"); + } + } + + // Test adding non-Attribute by various means after calling asList + for (Op op : Op.values()) { + AttributeList alist = new AttributeList(); + List lista = alist.asList(); + lista.add(new Attribute("foo", "bar")); + String what = op + " with non-Attribute after calling asList()"; + try { + doOp(alist, op); + fail(what + ": succeeded but should not have"); + } catch (IllegalArgumentException e) { + System.out.println("OK: " + what + ": got IllegalArgumentException"); + } + } + + if (failure == null) + System.out.println("TEST PASSED"); + else + throw new Exception("TEST FAILED: " + failure); + } + + private static enum Op { + ADD("add(Object)"), ADD_AT("add(int, Object)"), + ADD_ALL("add(Collection)"), ADD_ALL_AT("add(int, Collection)"), + SET("set(int, Object)"); + + private Op(String what) { + this.what = what; + } + + @Override + public String toString() { + return what; + } + + private final String what; + } + + private static void doOp(AttributeList alist, Op op) { + Object x = "oops"; + switch (op) { + case ADD: alist.add(x); break; + case ADD_AT: alist.add(0, x); break; + case ADD_ALL: alist.add(Collections.singleton(x)); break; + case ADD_ALL_AT: alist.add(0, Collections.singleton(x)); break; + case SET: alist.set(0, x); break; + default: throw new AssertionError("Case not covered"); + } + } + + private static void fail(String why) { + System.out.println("FAIL: " + why); + failure = why; + } + +} diff --git a/test/javax/management/context/ContextForwarderTest.java b/test/javax/management/context/ContextForwarderTest.java new file mode 100644 index 0000000000000000000000000000000000000000..d82acd3b63e5cb49dc400f778e23f415710d0a33 --- /dev/null +++ b/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/test/javax/management/context/ContextTest.java b/test/javax/management/context/ContextTest.java new file mode 100644 index 0000000000000000000000000000000000000000..1fb293ef57451ae6fd3b8f1532fca4ced3dbcd72 --- /dev/null +++ b/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/test/javax/management/context/LocaleAwareBroadcasterTest.java b/test/javax/management/context/LocaleAwareBroadcasterTest.java new file mode 100644 index 0000000000000000000000000000000000000000..7963f9f33b63acf88af3d8c643e6b3ad7547b0c0 --- /dev/null +++ b/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/test/javax/management/context/LocaleTest.java b/test/javax/management/context/LocaleTest.java new file mode 100644 index 0000000000000000000000000000000000000000..e5ea5309c216d4ef2fff4da2d30f69a9ad74ad29 --- /dev/null +++ b/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/test/javax/management/context/LocalizableTest.java b/test/javax/management/context/LocalizableTest.java new file mode 100644 index 0000000000000000000000000000000000000000..40a18af350de839695dd1593cdbae95a311bafd0 --- /dev/null +++ b/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/test/javax/management/context/RemoteContextTest.java b/test/javax/management/context/RemoteContextTest.java new file mode 100644 index 0000000000000000000000000000000000000000..56c7eab6c8bb827c92d33eda5cc0c75c1b335e08 --- /dev/null +++ b/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/test/javax/management/context/localizable/MBeanDescriptions.properties b/test/javax/management/context/localizable/MBeanDescriptions.properties new file mode 100644 index 0000000000000000000000000000000000000000..4722c5b5acc12cabe5644f140df30a7788cb750c --- /dev/null +++ b/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/test/javax/management/context/localizable/MBeanDescriptions_fr.java b/test/javax/management/context/localizable/MBeanDescriptions_fr.java new file mode 100644 index 0000000000000000000000000000000000000000..9e9cfa2ffea7527ccc3d2c7f6b1cde18b7e1a8a4 --- /dev/null +++ b/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/test/javax/management/context/localizable/Whatsit.java b/test/javax/management/context/localizable/Whatsit.java new file mode 100644 index 0000000000000000000000000000000000000000..f140ad920cb5c615ccd0fc6d5e171835334e9df6 --- /dev/null +++ b/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/test/javax/management/context/localizable/WhatsitMBean.java b/test/javax/management/context/localizable/WhatsitMBean.java new file mode 100644 index 0000000000000000000000000000000000000000..5bebc52f312eff396fab115e51ad1b9786d6c9aa --- /dev/null +++ b/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/test/javax/management/eventService/CustomForwarderTest.java b/test/javax/management/eventService/CustomForwarderTest.java index c9c866aaee076e5e723277d4e6315deebb515a94..4d7e3027e31a07c97a861217c16784ca5fe3c22e 100644 --- a/test/javax/management/eventService/CustomForwarderTest.java +++ b/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/test/javax/management/eventService/EventClientExecutorTest.java b/test/javax/management/eventService/EventClientExecutorTest.java index 19d6afdf8144e0d65c8249be6d1b84158e2c2a00..5201335e8b9f0e7a883d79529e9e1f5b96fc6b0d 100644 --- a/test/javax/management/eventService/EventClientExecutorTest.java +++ b/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/test/javax/management/eventService/EventManagerTest.java b/test/javax/management/eventService/EventManagerTest.java index 2717c0e51035f8de4140278e4d45935665a531cf..66e4e36cf4a3ef0ce1c043f46024f8a8e75b33e8 100644 --- a/test/javax/management/eventService/EventManagerTest.java +++ b/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/test/javax/management/eventService/ListenerTest.java b/test/javax/management/eventService/ListenerTest.java index 7195736b66264abd53e4303de2eee6ebe947cd8d..c4c91135673826136525df9e5cd2d3d5cf12eb14 100644 --- a/test/javax/management/eventService/ListenerTest.java +++ b/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/test/javax/management/eventService/NotSerializableNotifTest.java b/test/javax/management/eventService/NotSerializableNotifTest.java index 0bf0bc5faae6c6e545d998d9d510def15a292bb0..4185b4dd9e4aceac8db05bcf843fe03223630290 100644 --- a/test/javax/management/eventService/NotSerializableNotifTest.java +++ b/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/test/javax/management/eventService/UsingEventService.java b/test/javax/management/eventService/UsingEventService.java index 3d768ed8d6faa420b691f08d26e7aebd7f375fc6..b46ffe3f6d88331d70fc7e1ee7c509f2029e0273 100644 --- a/test/javax/management/eventService/UsingEventService.java +++ b/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/test/javax/management/namespace/EventWithNamespaceControlTest.java b/test/javax/management/namespace/EventWithNamespaceControlTest.java index 1e3901104c7462a2666e21e9708f064ab9a936e5..9bfa9866d855cb598e62a9666e8a77ad320caaae 100644 --- a/test/javax/management/namespace/EventWithNamespaceControlTest.java +++ b/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/test/javax/management/namespace/JMXNamespaceSecurityTest.java b/test/javax/management/namespace/JMXNamespaceSecurityTest.java index 213ffbfb63e734be4bb10ce6cd8ed85586e1e320..ec201dbda2c8304bdc8a0f07f96616725e3678fd 100644 --- a/test/javax/management/namespace/JMXNamespaceSecurityTest.java +++ b/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/test/javax/management/namespace/JMXNamespaceViewTest.java b/test/javax/management/namespace/JMXNamespaceViewTest.java index e134968296b9990c95694bd41b74ca11c6637dfe..4764f531f6fc387b081580820cf53496dff6b4a7 100644 --- a/test/javax/management/namespace/JMXNamespaceViewTest.java +++ b/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/test/javax/management/namespace/JMXRemoteTargetNamespace.java b/test/javax/management/namespace/JMXRemoteTargetNamespace.java index 3d83844b1ffe1eef1c21a33dc86010711b85981a..633e930214210712bacea0de4c7919532be82235 100644 --- a/test/javax/management/namespace/JMXRemoteTargetNamespace.java +++ b/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/test/javax/management/namespace/NamespaceNotificationsTest.java b/test/javax/management/namespace/NamespaceNotificationsTest.java index 9c5a1a04a1ca29bd26ba583b0ecf3b8127c1ad63..0545a36b2adf9ac1b920ec88ead4f9b64282925f 100644 --- a/test/javax/management/namespace/NamespaceNotificationsTest.java +++ b/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/test/javax/management/namespace/NullDomainObjectNameTest.java b/test/javax/management/namespace/NullDomainObjectNameTest.java index 3251d388a860cf07ed79729cef353270082562a3..6b1ad51c252737cd845a403847312c128e83c182 100644 --- a/test/javax/management/namespace/NullDomainObjectNameTest.java +++ b/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/test/javax/management/namespace/NullObjectNameTest.java b/test/javax/management/namespace/NullObjectNameTest.java index 156e7661db55b816880044563b415990f47ca977..0e645dc1e3c505f0f94f99a105db34a912d161ae 100644 --- a/test/javax/management/namespace/NullObjectNameTest.java +++ b/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/test/javax/management/openmbean/CompositeDataStringTest.java b/test/javax/management/openmbean/CompositeDataStringTest.java index 286bfd1d41bfdd932eaea446a82c16902fe15265..7a64e52305a2dfd573ee07689e8d2e6da3c66c06 100644 --- a/test/javax/management/openmbean/CompositeDataStringTest.java +++ b/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/test/javax/management/openmbean/CompositeDataToMapTest.java b/test/javax/management/openmbean/CompositeDataToMapTest.java new file mode 100644 index 0000000000000000000000000000000000000000..30641b7c72f4aca073117f8c5fe7b1018a31086e --- /dev/null +++ b/test/javax/management/openmbean/CompositeDataToMapTest.java @@ -0,0 +1,116 @@ +/* + * 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 6750472 6752563 + * @summary Test CompositeDataSupport.toMap. + * @author Eamonn McManus + * @run main/othervm -ea CompositeDataToMapTest + */ + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +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 CompositeDataToMapTest { + private static class IdentityInvocationHandler implements InvocationHandler { + private final Object wrapped; + + public IdentityInvocationHandler(Object wrapped) { + this.wrapped = wrapped; + } + + public Object invoke(Object proxy, Method m, Object[] args) + throws Throwable { + try { + return m.invoke(wrapped, args); + } catch (InvocationTargetException e) { + throw e.getCause(); + } + } + } + + private static T wrap(T x, Class intf) { + InvocationHandler ih = new IdentityInvocationHandler(x); + return intf.cast(Proxy.newProxyInstance( + intf.getClassLoader(), new Class[] {intf}, ih)); + } + + public static void main(String[] args) throws Exception { + if (!CompositeDataToMapTest.class.desiredAssertionStatus()) + throw new AssertionError("Must be run with -ea"); + + CompositeType emptyCT = new CompositeType( + "empty", "empty", new String[0], new String[0], new OpenType[0]); + CompositeData emptyCD = new CompositeDataSupport( + emptyCT, Collections.emptyMap()); + assert CompositeDataSupport.toMap(emptyCD).isEmpty() : + "Empty CD produces empty Map"; + + CompositeData emptyCD2 = new CompositeDataSupport( + emptyCT, new String[0], new Object[0]); + assert emptyCD.equals(emptyCD2) : "Empty CD can be constructed two ways"; + + CompositeType namedNumberCT = new CompositeType( + "NamedNumber", "NamedNumber", + new String[] {"name", "number"}, + new String[] {"name", "number"}, + new OpenType[] {SimpleType.STRING, SimpleType.INTEGER}); + Map namedNumberMap = new HashMap(); + namedNumberMap.put("name", "Deich"); + namedNumberMap.put("number", 10); + CompositeData namedNumberCD = new CompositeDataSupport( + namedNumberCT, namedNumberMap); + assert CompositeDataSupport.toMap(namedNumberCD).equals(namedNumberMap) : + "Map survives passage through CompositeData"; + + namedNumberCD = wrap(namedNumberCD, CompositeData.class); + assert CompositeDataSupport.toMap(namedNumberCD).equals(namedNumberMap) : + "Map survives passage through wrapped CompositeData"; + + namedNumberMap = CompositeDataSupport.toMap(namedNumberCD); + namedNumberMap.put("name", "Ceathar"); + namedNumberMap.put("number", 4); + namedNumberCD = new CompositeDataSupport(namedNumberCT, namedNumberMap); + assert CompositeDataSupport.toMap(namedNumberCD).equals(namedNumberMap) : + "Modified Map survives passage through CompositeData"; + + try { + namedNumberMap = CompositeDataSupport.toMap(null); + assert false : "Null toMap arg provokes exception"; + } catch (Exception e) { + assert e instanceof IllegalArgumentException : + "Exception for null toMap arg is IllegalArgumentException"; + } + } +} diff --git a/test/javax/management/remote/mandatory/connectorServer/ForwarderChainTest.java b/test/javax/management/remote/mandatory/connectorServer/ForwarderChainTest.java index b6c6d87926fade5670fce3242a6994b442c77d7d..e1dcbcb6e05087a49e5d35e46e00b22313d1709c 100644 --- a/test/javax/management/remote/mandatory/connectorServer/ForwarderChainTest.java +++ b/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/test/javax/management/remote/mandatory/connectorServer/StandardForwardersTest.java b/test/javax/management/remote/mandatory/connectorServer/StandardForwardersTest.java index d2d5e3771fff03e6d791dbbed93c758c6cd35c3f..25efde9dd0e4a4a53604f59b82beab08a127aed0 100644 --- a/test/javax/management/remote/mandatory/connectorServer/StandardForwardersTest.java +++ b/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/test/javax/management/remote/mandatory/provider/ProviderTest.java b/test/javax/management/remote/mandatory/provider/ProviderTest.java index 0c7e906b6a620c1c2ec888b7a858302b9731cdb0..90aa130001db587f864daa4c8f68aefc29470531 100644 --- a/test/javax/management/remote/mandatory/provider/ProviderTest.java +++ b/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/test/javax/management/remote/mandatory/subjectDelegation/SimpleStandard.java b/test/javax/management/remote/mandatory/subjectDelegation/SimpleStandard.java index 1a0e1dfe66d711f188657d5b58f59a13436f94e8..d0d75988b1e3d6e8c80eea595a31e4f48743ee92 100644 --- a/test/javax/management/remote/mandatory/subjectDelegation/SimpleStandard.java +++ b/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); } diff --git a/test/javax/management/remote/mandatory/version/JMXSpecVersionTest.java b/test/javax/management/remote/mandatory/version/JMXSpecVersionTest.java new file mode 100644 index 0000000000000000000000000000000000000000..69782690d0a9eed40108ed7b81de91b5018c5b70 --- /dev/null +++ b/test/javax/management/remote/mandatory/version/JMXSpecVersionTest.java @@ -0,0 +1,308 @@ +/* + * 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 6750008 + * @summary Test JMX.getSpecificationVersion + * @author Eamonn McManus + */ + +import java.io.IOException; +import java.util.Collections; +import java.util.ListIterator; +import java.util.Set; +import javax.management.Attribute; +import javax.management.AttributeList; +import javax.management.AttributeNotFoundException; +import javax.management.DynamicMBean; +import javax.management.InstanceNotFoundException; +import javax.management.InvalidAttributeValueException; +import javax.management.JMX; +import javax.management.MBeanException; +import javax.management.MBeanInfo; +import javax.management.MBeanServer; +import javax.management.MBeanServerConnection; +import javax.management.MBeanServerDelegate; +import javax.management.MBeanServerDelegateMBean; +import javax.management.MBeanServerFactory; +import javax.management.ObjectName; +import javax.management.ReflectionException; +import javax.management.StandardMBean; +import javax.management.namespace.JMXNamespace; +import javax.management.namespace.JMXNamespaces; +import javax.management.namespace.MBeanServerSupport; +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 JMXSpecVersionTest { + private static String failure; + private static final Object POISON_PILL = new Object(); + + private static class FakeDelegate implements DynamicMBean { + private final Object specVersion; + private final DynamicMBean delegate = new StandardMBean( + new MBeanServerDelegate(), MBeanServerDelegateMBean.class, false); + + FakeDelegate(Object specVersion) { + this.specVersion = specVersion; + } + + public Object getAttribute(String attribute) + throws AttributeNotFoundException, MBeanException, + ReflectionException { + if ("SpecificationVersion".equals(attribute)) { + if (specVersion == POISON_PILL) + throw new AttributeNotFoundException(attribute); + else + return specVersion; + } else + return delegate.getAttribute(attribute); + } + + public void setAttribute(Attribute attribute) + throws AttributeNotFoundException, InvalidAttributeValueException, + MBeanException, ReflectionException { + delegate.setAttribute(attribute); + } + + public AttributeList getAttributes(String[] attributes) { + AttributeList list = delegate.getAttributes(attributes); + for (ListIterator it = list.asList().listIterator(); + it.hasNext(); ) { + Attribute attr = it.next(); + if (attr.getName().equals("SpecificationVersion")) { + it.remove(); + if (specVersion != POISON_PILL) { + attr = new Attribute(attr.getName(), specVersion); + it.add(attr); + } + } + } + return list; + } + + public AttributeList setAttributes(AttributeList attributes) { + return delegate.setAttributes(attributes); + } + + public Object invoke(String actionName, Object[] params, + String[] signature) throws MBeanException, + ReflectionException { + return delegate.invoke(actionName, params, signature); + } + + public MBeanInfo getMBeanInfo() { + throw new UnsupportedOperationException("Not supported yet."); + } + } + + private static class MBeanServerWithVersion extends MBeanServerSupport { + private final DynamicMBean delegate; + + public MBeanServerWithVersion(Object specVersion) { + this.delegate = new FakeDelegate(specVersion); + } + + @Override + public DynamicMBean getDynamicMBeanFor(ObjectName name) + throws InstanceNotFoundException { + if (MBeanServerDelegate.DELEGATE_NAME.equals(name)) + return delegate; + else + throw new InstanceNotFoundException(name); + } + + @Override + protected Set getNames() { + return Collections.singleton(MBeanServerDelegate.DELEGATE_NAME); + } + } + + private static class EmptyMBeanServer extends MBeanServerSupport { + @Override + public DynamicMBean getDynamicMBeanFor(ObjectName name) throws InstanceNotFoundException { + throw new InstanceNotFoundException(name); + } + + @Override + protected Set getNames() { + return Collections.emptySet(); + } + } + + public static void main(String[] args) throws Exception { + MBeanServer mbs = MBeanServerFactory.newMBeanServer(); + JMXServiceURL url = new JMXServiceURL("service:jmx:rmi:///"); + JMXConnectorServer cs = JMXConnectorServerFactory.newJMXConnectorServer( + url, null, mbs); + cs.start(); + + String realVersion = (String) mbs.getAttribute( + MBeanServerDelegate.DELEGATE_NAME, "SpecificationVersion"); + assertEquals("Reported local version", + realVersion, JMX.getSpecificationVersion(mbs, null)); + assertEquals("Reported local version >= \"2.0\"", + true, (realVersion.compareTo("2.0") >= 0)); + + JMXConnector cc = JMXConnectorFactory.connect(cs.getAddress()); + MBeanServerConnection mbsc = cc.getMBeanServerConnection(); + assertEquals("Reported remote version", + realVersion, JMX.getSpecificationVersion(mbsc, null)); + + cc.close(); + try { + String brokenVersion = JMX.getSpecificationVersion(mbsc, null); + fail("JMX.getSpecificationVersion succeded over closed connection" + + " (returned " + brokenVersion + ")"); + } catch (Exception e) { + assertEquals("Exception for closed connection", + IOException.class, e.getClass()); + } + + try { + String brokenVersion = JMX.getSpecificationVersion( + new EmptyMBeanServer(), null); + fail("JMX.getSpecificationVersion succeded with empty MBean Server" + + " (returned " + brokenVersion + ")"); + } catch (Exception e) { + assertEquals("Exception for empty MBean Server", + IOException.class, e.getClass()); + } + + try { + String brokenVersion = JMX.getSpecificationVersion(null, null); + fail("JMX.getSpecificationVersion succeded with null MBean Server" + + " (returned " + brokenVersion + ")"); + } catch (Exception e) { + assertEquals("Exception for null MBean Server", + IllegalArgumentException.class, e.getClass()); + } + + MBeanServer mbs1_2 = new MBeanServerWithVersion("1.2"); + String version1_2 = JMX.getSpecificationVersion(mbs1_2, null); + assertEquals("Version for 1.2 MBean Server", "1.2", version1_2); + + // It's completely nutty for an MBean Server to return null as the + // value of its spec version, and we don't actually say what happens + // in that case, but in fact we return the null to the caller. + MBeanServer mbs_null = new MBeanServerWithVersion(null); + String version_null = JMX.getSpecificationVersion(mbs_null, null); + assertEquals("Version for MBean Server that declares null spec version", + null, version_null); + + try { + MBeanServer mbs1_2_float = new MBeanServerWithVersion(1.2f); + String version1_2_float = + JMX.getSpecificationVersion(mbs1_2_float, null); + fail("JMX.getSpecificationVersion succeeded with version 1.2f" + + " (returned " + version1_2_float + ")"); + } catch (Exception e) { + assertEquals("Exception for non-string version (1.2f)", + IOException.class, e.getClass()); + } + + try { + MBeanServer mbs_missing = new MBeanServerWithVersion(POISON_PILL); + String version_missing = + JMX.getSpecificationVersion(mbs_missing, null); + fail("JMX.getSpecificationVersion succeeded with null version" + + " (returned " + version_missing + ")"); + } catch (Exception e) { + assertEquals("Exception for missing version", + IOException.class, e.getClass()); + } + + ObjectName wildcardNamespaceName = new ObjectName("foo//*//bar//baz:k=v"); + try { + String brokenVersion = + JMX.getSpecificationVersion(mbsc, wildcardNamespaceName); + fail("JMX.getSpecificationVersion succeeded with wildcard namespace" + + " (returned " + brokenVersion + ")"); + } catch (Exception e) { + assertEquals("Exception for wildcard namespace", + IllegalArgumentException.class, e.getClass()); + } + + String sub1_2namespace = "blibby"; + JMXNamespace sub1_2 = new JMXNamespace(mbs1_2); + ObjectName sub1_2name = + JMXNamespaces.getNamespaceObjectName(sub1_2namespace); + mbs.registerMBean(sub1_2, sub1_2name); + String sub1_2namespaceHandlerVersion = + JMX.getSpecificationVersion(mbs, sub1_2name); + assertEquals("Spec version of namespace handler", + realVersion, sub1_2namespaceHandlerVersion); + // The namespace handler is in the top-level namespace so its + // version should not be 1.2. + + for (String nameInSub : new String[] {"*:*", "d:k=v"}) { + ObjectName subName = new ObjectName(sub1_2namespace + "//" + nameInSub); + String subVersion = JMX.getSpecificationVersion(mbs, subName); + assertEquals("Spec version in 1.2 namespace (" + nameInSub + ")", + "1.2", subVersion); + } + + mbs.unregisterMBean(sub1_2name); + for (String noSuchNamespace : new String[] { + sub1_2namespace + "//*:*", sub1_2namespace + "//d:k=v", + }) { + try { + String brokenVersion = JMX.getSpecificationVersion( + mbs, new ObjectName(noSuchNamespace)); + fail("JMX.getSpecificationVersion succeeded with missing " + + "namespace (" + noSuchNamespace + " -> " + + brokenVersion); + } catch (Exception e) { + assertEquals("Exception for missing namespace", + IOException.class, e.getClass()); + } + } + + if (failure != null) + throw new Exception("TEST FAILED: " + failure); + System.out.println("TEST PASSED"); + } + + private static void assertEquals(String what, Object expect, Object actual) { + if (equal(expect, actual)) + System.out.println("OK: " + what + ": " + expect); + else + fail(what + ": expected " + expect + ", got " + actual); + } + + private static boolean equal(Object x, Object y) { + if (x == null) + return (y == null); + else + return x.equals(y); + } + + private static void fail(String why) { + System.out.println("FAILED: " + why); + failure = why; + } +} diff --git a/test/sun/security/krb5/auto/Context.java b/test/sun/security/krb5/auto/Context.java index 9f52dad1f1a08183be268ce5faee1b4123501e97..2439aa240376edca54527db2055b820d12f0c705 100644 --- a/test/sun/security/krb5/auto/Context.java +++ b/test/sun/security/krb5/auto/Context.java @@ -109,13 +109,22 @@ public class Context { out.s = new Subject(); Krb5LoginModule krb5 = new Krb5LoginModule(); Map map = new HashMap(); - map.put("tryFirstPass", "true"); + Map shared = new HashMap(); + + if (pass != null) { + map.put("useFirstPass", "true"); + shared.put("javax.security.auth.login.name", user); + shared.put("javax.security.auth.login.password", pass); + } else { + map.put("doNotPrompt", "true"); + map.put("useTicketCache", "true"); + if (user != null) { + map.put("principal", user); + } + } if (storeKey) { map.put("storeKey", "true"); } - Map shared = new HashMap(); - shared.put("javax.security.auth.login.name", user); - shared.put("javax.security.auth.login.password", pass); krb5.initialize(out.s, null, shared, map); krb5.login(); @@ -360,6 +369,10 @@ public class Context { if (me.x.isEstablished()) { me.f = true; System.out.println(c.name + " side established"); + if (input != null) { + throw new Exception("Context established but " + + "still receive token at " + c.name); + } return null; } else { System.out.println(c.name + " call initSecContext"); @@ -374,6 +387,10 @@ public class Context { if (me.x.isEstablished()) { me.f = true; System.out.println(s.name + " side established"); + if (input != null) { + throw new Exception("Context established but " + + "still receive token at " + s.name); + } return null; } else { System.out.println(s.name + " called acceptSecContext"); diff --git a/test/sun/security/krb5/auto/KDC.java b/test/sun/security/krb5/auto/KDC.java index a875e090bb5b6d3d0366a9cac58a9994b4fd3d5e..3e6a7792644f12cd03704a7606f6fc8ac76c0c41 100644 --- a/test/sun/security/krb5/auto/KDC.java +++ b/test/sun/security/krb5/auto/KDC.java @@ -32,6 +32,7 @@ import java.util.*; import java.util.concurrent.*; import sun.security.krb5.*; import sun.security.krb5.internal.*; +import sun.security.krb5.internal.ccache.CredentialsCache; import sun.security.krb5.internal.crypto.KeyUsage; import sun.security.krb5.internal.ktab.KeyTab; import sun.security.util.DerInputStream; @@ -765,7 +766,29 @@ public class KDC { DerOutputStream out = new DerOutputStream(); out.write(DerValue.createTag(DerValue.TAG_APPLICATION, true, (byte)Krb5.KRB_AS_REP), asRep.asn1Encode()); - return out.toByteArray(); + byte[] result = out.toByteArray(); + + // Added feature: + // Write the current issuing TGT into a ccache file specified + // by the system property below. + String ccache = System.getProperty("test.kdc.save.ccache"); + if (ccache != null) { + asRep.encKDCRepPart = enc_part; + sun.security.krb5.internal.ccache.Credentials credentials = + new sun.security.krb5.internal.ccache.Credentials(asRep); + asReq.reqBody.cname.setRealm(getRealm()); + CredentialsCache cache = + CredentialsCache.create(asReq.reqBody.cname, ccache); + if (cache == null) { + throw new IOException("Unable to create the cache file " + + ccache); + } + cache.update(credentials); + cache.save(); + new File(ccache).deleteOnExit(); + } + + return result; } catch (KrbException ke) { ke.printStackTrace(System.out); KRBError kerr = ke.getError(); diff --git a/test/sun/security/krb5/auto/LoginModuleOptions.java b/test/sun/security/krb5/auto/LoginModuleOptions.java new file mode 100644 index 0000000000000000000000000000000000000000..4c0dd73b8d5d649f2dd181b79bb0f11377719368 --- /dev/null +++ b/test/sun/security/krb5/auto/LoginModuleOptions.java @@ -0,0 +1,184 @@ +/* + * 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 6765491 + * @summary Krb5LoginModule a little too restrictive, and the doc is not clear. + */ +import com.sun.security.auth.module.Krb5LoginModule; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import javax.security.auth.Subject; +import javax.security.auth.callback.Callback; +import javax.security.auth.callback.CallbackHandler; +import javax.security.auth.callback.NameCallback; +import javax.security.auth.callback.PasswordCallback; +import javax.security.auth.callback.UnsupportedCallbackException; + +public class LoginModuleOptions { + + private static final String NAME = "javax.security.auth.login.name"; + private static final String PWD = "javax.security.auth.login.password"; + + public static void main(String[] args) throws Exception { + OneKDC kdc = new OneKDC(null); + kdc.addPrincipal("foo", "bar".toCharArray()); + kdc.writeKtab(OneKDC.KTAB); // rewrite to add foo + + // All 4 works: keytab, shared state, callback, cache + login(null, "useKeyTab", "true", "principal", "dummy"); + login(null, "tryFirstPass", "true", NAME, OneKDC.USER, + PWD, OneKDC.PASS); + System.setProperty("test.kdc.save.ccache", "krbcc"); + login(new MyCallback(OneKDC.USER, OneKDC.PASS)); // save the cache + System.clearProperty("test.kdc.save.ccache"); + login(null, "useTicketCache", "true", "ticketCache", "krbcc"); + + // Fallbacks + // 1. ccache -> keytab + login(null, "useTicketCache", "true", "ticketCache", "krbcc_non_exists", + "useKeyTab", "true", "principal", "dummy"); + // 2. keytab -> shared + login(null, "useKeyTab", "true", "principal", "dummy", + "keyTab", "ktab_non_exist", + "tryFirstPass", "true", NAME, OneKDC.USER, PWD, OneKDC.PASS); + // 3. shared -> callback + // 3.1. useFirstPass, no callback + boolean failed = false; + try { + login(new MyCallback(OneKDC.USER, OneKDC.PASS), + "useFirstPass", "true", + NAME, OneKDC.USER, PWD, "haha".toCharArray()); + } catch (Exception e) { + failed = true; + } + if (!failed) { + throw new Exception("useFirstPass should not fallback to callback"); + } + // 3.2. tryFirstPass, has callback + login(new MyCallback(OneKDC.USER, OneKDC.PASS), + "tryFirstPass", "true", + NAME, OneKDC.USER, PWD, "haha".toCharArray()); + + // Preferences of type + // 1. ccache preferred to keytab + login(new MyCallback("foo", null), + "useTicketCache", "true", "ticketCache", "krbcc", + "useKeyTab", "true"); + // 2. keytab preferred to shared. This test case is not exactly correct, + // because principal=dummy would shadow the PWD setting in the shared + // state. So by only looking at the final authentication user name + // (which is how this program does), there's no way to tell if keyTab + // is picked first, or shared is tried first but fallback to keytab. + login(null, "useKeyTab", "true", "principal", "dummy", + "tryFirstPass", "true", NAME, "foo", PWD, "bar".toCharArray()); + // 3. shared preferred to callback + login(new MyCallback("foo", "bar".toCharArray()), + "tryFirstPass", "true", NAME, OneKDC.USER, PWD, OneKDC.PASS); + + // Preferences of username + // 1. principal preferred to NAME (NAME can be wrong or missing) + login(null, "principal", OneKDC.USER, + "tryFirstPass", "true", NAME, "someone_else", PWD, OneKDC.PASS); + login(null, "principal", OneKDC.USER, + "tryFirstPass", "true", PWD, OneKDC.PASS); + // 2. NAME preferred to callback + login(new MyCallback("someone_else", OneKDC.PASS), + "principal", OneKDC.USER); + // 3. With tryFirstPass, NAME preferred to callback + login(new MyCallback("someone_else", null), + "tryFirstPass", "true", NAME, OneKDC.USER, PWD, OneKDC.PASS); + // 3.1. you must provide a NAME (when there's no principal) + failed = false; + try { + login(new MyCallback(OneKDC.USER, null), + "tryFirstPass", "true", PWD, OneKDC.PASS); + } catch (Exception e) { + failed = true; + } + if (!failed) { + throw new Exception("useFirstPass must provide a NAME"); + } + // 3.2 Hybrid, you can use NAME as "", and provide it using callback. + // I don't think this is designed. + login(new MyCallback(OneKDC.USER, null), + "tryFirstPass", "true", NAME, "", PWD, OneKDC.PASS); + + // Test for the bug fix: doNotPrompt can be true if tryFirstPass=true + login(null, "doNotPrompt", "true", "storeKey", "true", + "tryFirstPass", "true", NAME, OneKDC.USER, PWD, OneKDC.PASS); + } + + static void login(CallbackHandler callback, Object... options) + throws Exception { + Krb5LoginModule krb5 = new Krb5LoginModule(); + Subject subject = new Subject(); + Map map = new HashMap(); + Map shared = new HashMap(); + + int count = options.length / 2; + for (int i = 0; i < count; i++) { + String key = (String) options[2 * i]; + Object value = options[2 * i + 1]; + if (key.startsWith("javax")) { + shared.put(key, value); + } else { + map.put(key, (String) value); + } + } + krb5.initialize(subject, callback, shared, map); + krb5.login(); + krb5.commit(); + if (!subject.getPrincipals().iterator().next() + .getName().startsWith(OneKDC.USER)) { + throw new Exception("The authenticated is not " + OneKDC.USER); + } + } + + static class MyCallback implements CallbackHandler { + + private String name; + private char[] password; + + public MyCallback(String name, char[] password) { + this.name = name; + this.password = password; + } + + public void handle(Callback[] callbacks) { + for (Callback callback : callbacks) { + System.err.println(callback); + if (callback instanceof NameCallback) { + System.err.println("name is " + name); + ((NameCallback) callback).setName(name); + } + if (callback instanceof PasswordCallback) { + System.err.println("pass is " + new String(password)); + ((PasswordCallback) callback).setPassword(password); + } + } + } + } +} diff --git a/test/sun/security/krb5/auto/NonMutualSpnego.java b/test/sun/security/krb5/auto/NonMutualSpnego.java new file mode 100644 index 0000000000000000000000000000000000000000..f2e7812c3ed317ee375d7b200057a6d85e9b6cb5 --- /dev/null +++ b/test/sun/security/krb5/auto/NonMutualSpnego.java @@ -0,0 +1,58 @@ +/* + * 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 6733095 + * @summary Failure when SPNEGO request non-Mutual + */ + +import sun.security.jgss.GSSUtil; + +public class NonMutualSpnego { + + public static void main(String[] args) + throws Exception { + + // Create and start the KDC + new OneKDC(null).writeJAASConf(); + new NonMutualSpnego().go(); + } + + void go() throws Exception { + Context c = Context.fromJAAS("client"); + Context s = Context.fromJAAS("server"); + + c.startAsClient(OneKDC.SERVER, GSSUtil.GSS_SPNEGO_MECH_OID); + c.x().requestMutualAuth(false); + s.startAsServer(GSSUtil.GSS_SPNEGO_MECH_OID); + + Context.handshake(c, s); + + Context.transmit("i say high --", c, s); + Context.transmit(" you say low", s, c); + + c.dispose(); + s.dispose(); + } +}