jdk/src/share/classes/com/sun/jmx/namespace/NamespaceInterceptor.java
changeset 1156 bbc2d15aaf7a
child 1222 78e3d021d528
--- /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;
+        }
+    }
+
+}