--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/share/classes/com/sun/jmx/namespace/NamespaceInterceptor.java Thu Sep 04 14:46:36 2008 +0200
@@ -0,0 +1,449 @@
+/*
+ * 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.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+import java.util.UUID;
+import java.util.logging.Logger;
+
+import javax.management.Attribute;
+import javax.management.AttributeList;
+import javax.management.MBeanServer;
+import javax.management.MBeanServerConnection;
+import javax.management.MalformedObjectNameException;
+import javax.management.ObjectName;
+import javax.management.QueryExp;
+import javax.management.namespace.JMXNamespaces;
+import javax.management.namespace.JMXNamespace;
+import javax.management.namespace.JMXNamespacePermission;
+
+/**
+ * A NamespaceInterceptor wraps a JMXNamespace, performing
+ * ObjectName rewriting.
+ * <p><b>
+ * This API is a Sun internal API and is subject to changes without notice.
+ * </b></p>
+ * @since 1.7
+ */
+public class NamespaceInterceptor extends HandlerInterceptor<JMXNamespace> {
+
+ /**
+ * A logger for this class.
+ **/
+ private static final Logger LOG = JmxProperties.NAMESPACE_LOGGER;
+ private static final Logger PROBE_LOG = Logger.getLogger(
+ JmxProperties.NAMESPACE_LOGGER+".probe");
+
+ // The target name space in which the NamepsaceHandler is mounted.
+ private final String targetNs;
+
+ private final String serverName;
+
+ private final ObjectNameRouter proc;
+
+ /**
+ * Internal hack. The JMXRemoteNamespace can be closed and reconnected.
+ * Each time the JMXRemoteNamespace connects, a probe should be sent
+ * to detect cycle. The MBeanServer exposed by JMXRemoteNamespace thus
+ * implements the DynamicProbe interface, which makes it possible for
+ * this handler to know that it should send a new probe.
+ *
+ * XXX: TODO this probe thing is way too complex and fragile.
+ * This *must* go away or be replaced by something simpler.
+ * ideas are welcomed.
+ **/
+ public static interface DynamicProbe {
+ public boolean isProbeRequested();
+ }
+
+ /**
+ * Creates a new instance of NamespaceInterceptor
+ */
+ public NamespaceInterceptor(
+ String serverName,
+ JMXNamespace handler,
+ String targetNamespace) {
+ super(handler);
+ this.serverName = serverName;
+ this.targetNs =
+ ObjectNameRouter.normalizeNamespacePath(targetNamespace,
+ true, true, false);
+ proc = new ObjectNameRouter(targetNamespace, "");
+ }
+
+ @Override
+ public String toString() {
+ return this.getClass().getName()+"(parent="+serverName+
+ ", namespace="+this.targetNs+")";
+ }
+
+ /*
+ * XXX: TODO this probe thing is way too complex and fragile.
+ * This *must* go away or be replaced by something simpler.
+ * ideas are welcomed.
+ */
+ private volatile boolean probed = false;
+ private volatile ObjectName probe;
+
+ // Query Pattern that we will send through the source server in order
+ // to detect self-linking namespaces.
+ //
+ // XXX: TODO this probe thing is way too complex and fragile.
+ // This *must* go away or be replaced by something simpler.
+ // ideas are welcomed.
+ final ObjectName makeProbePattern(ObjectName probe)
+ throws MalformedObjectNameException {
+
+ // we could probably link the probe pattern with the probe - e.g.
+ // using the UUID as key in the pattern - but is it worth it? it
+ // also has some side effects on the context namespace - because
+ // such a probe may get rejected by the jmx.context// namespace.
+ //
+ // The trick here is to devise a pattern that is not likely to
+ // be blocked by intermediate levels. Querying for all namespace
+ // handlers in the source (or source namespace) is more likely to
+ // achieve this goal.
+ //
+ return ObjectName.getInstance("*" +
+ JMXNamespaces.NAMESPACE_SEPARATOR + ":" +
+ JMXNamespace.TYPE_ASSIGNMENT);
+ }
+
+ // tell whether the name pattern corresponds to what might have been
+ // sent as a probe.
+ // XXX: TODO this probe thing is way too complex and fragile.
+ // This *must* go away or be replaced by something simpler.
+ // ideas are welcomed.
+ final boolean isProbePattern(ObjectName name) {
+ final ObjectName p = probe;
+ if (p == null) return false;
+ try {
+ return String.valueOf(name).endsWith(targetNs+
+ JMXNamespaces.NAMESPACE_SEPARATOR + "*" +
+ JMXNamespaces.NAMESPACE_SEPARATOR + ":" +
+ JMXNamespace.TYPE_ASSIGNMENT);
+ } catch (RuntimeException x) {
+ // should not happen.
+ PROBE_LOG.finest("Ignoring unexpected exception in self link detection: "+
+ x);
+ return false;
+ }
+ }
+
+ // The first time a request reaches this NamespaceInterceptor, the
+ // interceptor will send a probe to detect whether the underlying
+ // JMXNamespace links to itslef.
+ //
+ // One way to create such self-linking namespace would be for instance
+ // to create a JMXNamespace whose getSourceServer() method would return:
+ // JMXNamespaces.narrowToNamespace(getMBeanServer(),
+ // getObjectName().getDomain())
+ //
+ // If such an MBeanServer is returned, then any call to that MBeanServer
+ // will trigger an infinite loop.
+ // There can be even trickier configurations if remote connections are
+ // involved.
+ //
+ // In order to prevent this from happening, the NamespaceInterceptor will
+ // send a probe, in an attempt to detect whether it will receive it at
+ // the other end. If the probe is received, an exception will be thrown
+ // in order to break the recursion. The probe is only sent once - when
+ // the first request to the namespace occurs. The DynamicProbe interface
+ // can also be used by a Sun JMXNamespace implementation to request the
+ // emission of a probe at any time (see JMXRemoteNamespace
+ // implementation).
+ //
+ // Probes work this way: the NamespaceInterceptor sets a flag and sends
+ // a queryNames() request. If a queryNames() request comes in when the flag
+ // is on, then it deduces that there is a self-linking loop - and instead
+ // of calling queryNames() on the source MBeanServer of the JMXNamespace
+ // handler (which would cause the loop to go on) it breaks the recursion
+ // by returning the probe ObjectName.
+ // If the NamespaceInterceptor receives the probe ObjectName as result of
+ // its original sendProbe() request it knows that it has been looping
+ // back on itslef and throws an IOException...
+ //
+ //
+ // XXX: TODO this probe thing is way too complex and fragile.
+ // This *must* go away or be replaced by something simpler.
+ // ideas are welcomed.
+ //
+ final void sendProbe(MBeanServerConnection msc)
+ throws IOException {
+ try {
+ PROBE_LOG.fine("Sending probe");
+
+ // This is just to prevent any other thread to modify
+ // the probe while the detection cycle is in progress.
+ //
+ final ObjectName probePattern;
+ // we don't want to synchronize on this - we use targetNs
+ // because it's non null and final.
+ synchronized (targetNs) {
+ probed = false;
+ if (probe != null) {
+ throw new IOException("concurent connection in progress");
+ }
+ final String uuid = UUID.randomUUID().toString();
+ final String endprobe =
+ JMXNamespaces.NAMESPACE_SEPARATOR + uuid +
+ ":type=Probe,key="+uuid;
+ final ObjectName newprobe =
+ ObjectName.getInstance(endprobe);
+ probePattern = makeProbePattern(newprobe);
+ probe = newprobe;
+ }
+
+ try {
+ PROBE_LOG.finer("Probe query: "+probePattern+" expecting: "+probe);
+ final Set<ObjectName> res = msc.queryNames(probePattern, null);
+ final ObjectName expected = probe;
+ PROBE_LOG.finer("Probe res: "+res);
+ if (res.contains(expected)) {
+ throw new IOException("namespace " +
+ targetNs + " is linking to itself: " +
+ "cycle detected by probe");
+ }
+ } catch (SecurityException x) {
+ PROBE_LOG.finer("Can't check for cycles: " + x);
+ // can't do anything....
+ } catch (RuntimeException x) {
+ PROBE_LOG.finer("Exception raised by queryNames: " + x);
+ throw x;
+ } finally {
+ probe = null;
+ }
+ } catch (MalformedObjectNameException x) {
+ final IOException io =
+ new IOException("invalid name space: probe failed");
+ io.initCause(x);
+ throw io;
+ }
+ PROBE_LOG.fine("Probe returned - no cycles");
+ probed = true;
+ }
+
+ // allows a Sun implementation JMX Namespace, such as the
+ // JMXRemoteNamespace, to control when a probe should be sent.
+ //
+ // XXX: TODO this probe thing is way too complex and fragile.
+ // This *must* go away or be replaced by something simpler.
+ // ideas are welcomed.
+ private boolean isProbeRequested(Object o) {
+ if (o instanceof DynamicProbe)
+ return ((DynamicProbe)o).isProbeRequested();
+ return false;
+ }
+
+ /**
+ * This method will send a probe to detect self-linking name spaces.
+ * A self linking namespace is a namespace that links back directly
+ * on itslef. Calling a method on such a name space always results
+ * in an infinite loop going through:
+ * [1]MBeanServer -> [2]NamespaceDispatcher -> [3]NamespaceInterceptor
+ * [4]JMXNamespace -> { network // or cd // or ... } -> [5]MBeanServer
+ * with exactly the same request than [1]...
+ *
+ * The namespace interceptor [2] tries to detect such condition the
+ * *first time* that the connection is used. It does so by setting
+ * a flag, and sending a queryNames() through the name space. If the
+ * queryNames comes back, it knows that there's a loop.
+ *
+ * The DynamicProbe interface can also be used by a Sun JMXNamespace
+ * implementation to request the emission of a probe at any time
+ * (see JMXRemoteNamespace implementation).
+ */
+ private MBeanServer connection() {
+ try {
+ final MBeanServer c = super.source();
+ if (probe != null) // should not happen
+ throw new RuntimeException("connection is being probed");
+
+ if (probed == false || isProbeRequested(c)) {
+ try {
+ // Should not happen if class well behaved.
+ // Never probed. Force it.
+ //System.err.println("sending probe for " +
+ // "target="+targetNs+", source="+srcNs);
+ sendProbe(c);
+ } catch (IOException io) {
+ throw new RuntimeException(io.getMessage(), io);
+ }
+ }
+
+ if (c != null) {
+ return c;
+ }
+ } catch (RuntimeException x) {
+ throw x;
+ }
+ throw new NullPointerException("getMBeanServerConnection");
+ }
+
+
+ @Override
+ protected MBeanServer source() {
+ return connection();
+ }
+
+ @Override
+ protected MBeanServer getServerForLoading() {
+ // don't want to send probe on getClassLoader/getClassLoaderFor
+ return super.source();
+ }
+
+ /**
+ * Calls {@link MBeanServerConnection#queryNames queryNames}
+ * on the underlying
+ * {@link #getMBeanServerConnection MBeanServerConnection}.
+ **/
+ @Override
+ public final Set<ObjectName> queryNames(ObjectName name, QueryExp query) {
+ // XXX: TODO this probe thing is way too complex and fragile.
+ // This *must* go away or be replaced by something simpler.
+ // ideas are welcomed.
+ PROBE_LOG.finer("probe is: "+probe+" pattern is: "+name);
+ if (probe != null && isProbePattern(name)) {
+ PROBE_LOG.finer("Return probe: "+probe);
+ return Collections.singleton(probe);
+ }
+ return super.queryNames(name, query);
+ }
+
+ @Override
+ protected ObjectName toSource(ObjectName targetName)
+ throws MalformedObjectNameException {
+ return proc.toSourceContext(targetName, true);
+ }
+
+ @Override
+ protected ObjectName toTarget(ObjectName sourceName)
+ throws MalformedObjectNameException {
+ return proc.toTargetContext(sourceName, false);
+ }
+
+ //
+ // Implements permission checks.
+ //
+ @Override
+ void check(ObjectName routingName, String member, String action) {
+ final SecurityManager sm = System.getSecurityManager();
+ if (sm == null) return;
+ if ("getDomains".equals(action)) return;
+ final JMXNamespacePermission perm =
+ new JMXNamespacePermission(serverName,member,
+ routingName,action);
+ sm.checkPermission(perm);
+ }
+
+ @Override
+ void checkCreate(ObjectName routingName, String className, String action) {
+ final SecurityManager sm = System.getSecurityManager();
+ if (sm == null) return;
+ final JMXNamespacePermission perm =
+ new JMXNamespacePermission(serverName,className,
+ routingName,action);
+ sm.checkPermission(perm);
+ }
+
+ //
+ // Implements permission filters for attributes...
+ //
+ @Override
+ AttributeList checkAttributes(ObjectName routingName,
+ AttributeList attributes, String action) {
+ check(routingName,null,action);
+ if (attributes == null || attributes.isEmpty()) return attributes;
+ final SecurityManager sm = System.getSecurityManager();
+ if (sm == null) return attributes;
+ final AttributeList res = new AttributeList();
+ for (Attribute at : attributes.asList()) {
+ try {
+ check(routingName,at.getName(),action);
+ res.add(at);
+ } catch (SecurityException x) { // DLS: OK
+ continue;
+ }
+ }
+ return res;
+ }
+
+ //
+ // Implements permission filters for attributes...
+ //
+ @Override
+ String[] checkAttributes(ObjectName routingName, String[] attributes,
+ String action) {
+ check(routingName,null,action);
+ if (attributes == null || attributes.length==0) return attributes;
+ final SecurityManager sm = System.getSecurityManager();
+ if (sm == null) return attributes;
+ final List<String> res = new ArrayList<String>(attributes.length);
+ for (String at : attributes) {
+ try {
+ check(routingName,at,action);
+ res.add(at);
+ } catch (SecurityException x) { // DLS: OK
+ continue;
+ }
+ }
+ return res.toArray(new String[res.size()]);
+ }
+
+ //
+ // Implements permission filters for domains...
+ //
+ @Override
+ String[] checkDomains(String[] domains, String action) {
+ // in principle, this method is never called because
+ // getDomains() will never be called - since there's
+ // no way that MBeanServer.getDomains() can be routed
+ // to a NamespaceInterceptor.
+ //
+ // This is also why there's no getDomains() in a
+ // JMXNamespacePermission...
+ //
+ return super.checkDomains(domains, action);
+ }
+
+ //
+ // Implements permission filters for queries...
+ //
+ @Override
+ boolean checkQuery(ObjectName routingName, String action) {
+ try {
+ check(routingName,null,action);
+ return true;
+ } catch (SecurityException x) { // DLS: OK
+ return false;
+ }
+ }
+
+}