NamespaceInterceptor.java 16.9 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445
/*
 * 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> {

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

}