jdk/src/share/classes/com/sun/jmx/namespace/NamespaceInterceptor.java
changeset 1156 bbc2d15aaf7a
child 1222 78e3d021d528
equal deleted inserted replaced
1155:a9a142fcf1b5 1156:bbc2d15aaf7a
       
     1 /*
       
     2  * Copyright 2008 Sun Microsystems, Inc.  All Rights Reserved.
       
     3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
       
     4  *
       
     5  * This code is free software; you can redistribute it and/or modify it
       
     6  * under the terms of the GNU General Public License version 2 only, as
       
     7  * published by the Free Software Foundation.  Sun designates this
       
     8  * particular file as subject to the "Classpath" exception as provided
       
     9  * by Sun in the LICENSE file that accompanied this code.
       
    10  *
       
    11  * This code is distributed in the hope that it will be useful, but WITHOUT
       
    12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
       
    13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
       
    14  * version 2 for more details (a copy is included in the LICENSE file that
       
    15  * accompanied this code).
       
    16  *
       
    17  * You should have received a copy of the GNU General Public License version
       
    18  * 2 along with this work; if not, write to the Free Software Foundation,
       
    19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
       
    20  *
       
    21  * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
       
    22  * CA 95054 USA or visit www.sun.com if you need additional information or
       
    23  * have any questions.
       
    24  */
       
    25 package com.sun.jmx.namespace;
       
    26 
       
    27 import com.sun.jmx.defaults.JmxProperties;
       
    28 import java.io.IOException;
       
    29 import java.util.ArrayList;
       
    30 import java.util.Collections;
       
    31 import java.util.List;
       
    32 import java.util.Set;
       
    33 import java.util.UUID;
       
    34 import java.util.logging.Logger;
       
    35 
       
    36 import javax.management.Attribute;
       
    37 import javax.management.AttributeList;
       
    38 import javax.management.MBeanServer;
       
    39 import javax.management.MBeanServerConnection;
       
    40 import javax.management.MalformedObjectNameException;
       
    41 import javax.management.ObjectName;
       
    42 import javax.management.QueryExp;
       
    43 import javax.management.namespace.JMXNamespaces;
       
    44 import javax.management.namespace.JMXNamespace;
       
    45 import javax.management.namespace.JMXNamespacePermission;
       
    46 
       
    47 /**
       
    48  * A NamespaceInterceptor wraps a JMXNamespace, performing
       
    49  * ObjectName rewriting.
       
    50  * <p><b>
       
    51  * This API is a Sun internal API and is subject to changes without notice.
       
    52  * </b></p>
       
    53  * @since 1.7
       
    54  */
       
    55 public class NamespaceInterceptor extends HandlerInterceptor<JMXNamespace> {
       
    56 
       
    57     /**
       
    58      * A logger for this class.
       
    59      **/
       
    60     private static final Logger LOG = JmxProperties.NAMESPACE_LOGGER;
       
    61     private static final Logger PROBE_LOG = Logger.getLogger(
       
    62             JmxProperties.NAMESPACE_LOGGER+".probe");
       
    63 
       
    64     // The target name space in which the NamepsaceHandler is mounted.
       
    65     private final String           targetNs;
       
    66 
       
    67     private final String           serverName;
       
    68 
       
    69     private final ObjectNameRouter proc;
       
    70 
       
    71     /**
       
    72      * Internal hack. The JMXRemoteNamespace can be closed and reconnected.
       
    73      * Each time the JMXRemoteNamespace connects, a probe should be sent
       
    74      * to detect cycle. The MBeanServer exposed by JMXRemoteNamespace thus
       
    75      * implements the DynamicProbe interface, which makes it possible for
       
    76      * this handler to know that it should send a new probe.
       
    77      *
       
    78      * XXX: TODO this probe thing is way too complex and fragile.
       
    79      *      This *must* go away or be replaced by something simpler.
       
    80      *      ideas are welcomed.
       
    81      **/
       
    82     public static interface DynamicProbe {
       
    83         public boolean isProbeRequested();
       
    84     }
       
    85 
       
    86     /**
       
    87      * Creates a new instance of NamespaceInterceptor
       
    88      */
       
    89     public NamespaceInterceptor(
       
    90             String serverName,
       
    91             JMXNamespace handler,
       
    92             String targetNamespace) {
       
    93         super(handler);
       
    94         this.serverName = serverName;
       
    95         this.targetNs =
       
    96                 ObjectNameRouter.normalizeNamespacePath(targetNamespace,
       
    97                 true, true, false);
       
    98         proc = new ObjectNameRouter(targetNamespace, "");
       
    99     }
       
   100 
       
   101     @Override
       
   102     public String toString() {
       
   103         return this.getClass().getName()+"(parent="+serverName+
       
   104                 ", namespace="+this.targetNs+")";
       
   105     }
       
   106 
       
   107     /*
       
   108      * XXX: TODO this probe thing is way too complex and fragile.
       
   109      *      This *must* go away or be replaced by something simpler.
       
   110      *      ideas are welcomed.
       
   111      */
       
   112     private volatile boolean probed = false;
       
   113     private volatile ObjectName probe;
       
   114 
       
   115     // Query Pattern that we will send through the source server in order
       
   116     // to detect self-linking namespaces.
       
   117     //
       
   118     // XXX: TODO this probe thing is way too complex and fragile.
       
   119     //      This *must* go away or be replaced by something simpler.
       
   120     //      ideas are welcomed.
       
   121     final ObjectName makeProbePattern(ObjectName probe)
       
   122             throws MalformedObjectNameException {
       
   123 
       
   124         // we could probably link the probe pattern with the probe - e.g.
       
   125         // using the UUID as key in the pattern - but is it worth it? it
       
   126         // also has some side effects on the context namespace - because
       
   127         // such a probe may get rejected by the jmx.context// namespace.
       
   128         //
       
   129         // The trick here is to devise a pattern that is not likely to
       
   130         // be blocked by intermediate levels. Querying for all namespace
       
   131         // handlers in the source (or source namespace) is more likely to
       
   132         // achieve this goal.
       
   133         //
       
   134         return ObjectName.getInstance("*" +
       
   135                 JMXNamespaces.NAMESPACE_SEPARATOR + ":" +
       
   136                 JMXNamespace.TYPE_ASSIGNMENT);
       
   137     }
       
   138 
       
   139     // tell whether the name pattern corresponds to what might have been
       
   140     // sent as a probe.
       
   141     // XXX: TODO this probe thing is way too complex and fragile.
       
   142     //      This *must* go away or be replaced by something simpler.
       
   143     //      ideas are welcomed.
       
   144     final boolean isProbePattern(ObjectName name) {
       
   145         final ObjectName p = probe;
       
   146         if (p == null) return false;
       
   147         try {
       
   148             return String.valueOf(name).endsWith(targetNs+
       
   149                 JMXNamespaces.NAMESPACE_SEPARATOR + "*" +
       
   150                 JMXNamespaces.NAMESPACE_SEPARATOR + ":" +
       
   151                 JMXNamespace.TYPE_ASSIGNMENT);
       
   152         } catch (RuntimeException x) {
       
   153             // should not happen.
       
   154             PROBE_LOG.finest("Ignoring unexpected exception in self link detection: "+
       
   155                     x);
       
   156             return false;
       
   157         }
       
   158     }
       
   159 
       
   160     // The first time a request reaches this NamespaceInterceptor, the
       
   161     // interceptor will send a probe to detect whether the underlying
       
   162     // JMXNamespace links to itslef.
       
   163     //
       
   164     // One way to create such self-linking namespace would be for instance
       
   165     // to create a JMXNamespace whose getSourceServer() method would return:
       
   166     // JMXNamespaces.narrowToNamespace(getMBeanServer(),
       
   167     //                                 getObjectName().getDomain())
       
   168     //
       
   169     // If such an MBeanServer is returned, then any call to that MBeanServer
       
   170     // will trigger an infinite loop.
       
   171     // There can be even trickier configurations if remote connections are
       
   172     // involved.
       
   173     //
       
   174     // In order to prevent this from happening, the NamespaceInterceptor will
       
   175     // send a probe, in an attempt to detect whether it will receive it at
       
   176     // the other end. If the probe is received, an exception will be thrown
       
   177     // in order to break the recursion. The probe is only sent once - when
       
   178     // the first request to the namespace occurs. The DynamicProbe interface
       
   179     // can also be used by a Sun JMXNamespace implementation to request the
       
   180     // emission of a probe at any time (see JMXRemoteNamespace
       
   181     // implementation).
       
   182     //
       
   183     // Probes work this way: the NamespaceInterceptor sets a flag and sends
       
   184     // a queryNames() request. If a queryNames() request comes in when the flag
       
   185     // is on, then it deduces that there is a self-linking loop - and instead
       
   186     // of calling queryNames() on the source MBeanServer of the JMXNamespace
       
   187     // handler (which would cause the loop to go on) it breaks the recursion
       
   188     // by returning the probe ObjectName.
       
   189     // If the NamespaceInterceptor receives the probe ObjectName as result of
       
   190     // its original sendProbe() request it knows that it has been looping
       
   191     // back on itslef and throws an IOException...
       
   192     //
       
   193     //
       
   194     // XXX: TODO this probe thing is way too complex and fragile.
       
   195     //      This *must* go away or be replaced by something simpler.
       
   196     //      ideas are welcomed.
       
   197     //
       
   198     final void sendProbe(MBeanServerConnection msc)
       
   199             throws IOException {
       
   200         try {
       
   201             PROBE_LOG.fine("Sending probe");
       
   202 
       
   203             // This is just to prevent any other thread to modify
       
   204             // the probe while the detection cycle is in progress.
       
   205             //
       
   206             final ObjectName probePattern;
       
   207             // we don't want to synchronize on this - we use targetNs
       
   208             // because it's non null and final.
       
   209             synchronized (targetNs) {
       
   210                 probed = false;
       
   211                 if (probe != null) {
       
   212                     throw new IOException("concurent connection in progress");
       
   213                 }
       
   214                 final String uuid = UUID.randomUUID().toString();
       
   215                 final String endprobe =
       
   216                         JMXNamespaces.NAMESPACE_SEPARATOR + uuid +
       
   217                         ":type=Probe,key="+uuid;
       
   218                 final ObjectName newprobe =
       
   219                         ObjectName.getInstance(endprobe);
       
   220                 probePattern = makeProbePattern(newprobe);
       
   221                 probe = newprobe;
       
   222             }
       
   223 
       
   224             try {
       
   225                 PROBE_LOG.finer("Probe query: "+probePattern+" expecting: "+probe);
       
   226                 final Set<ObjectName> res = msc.queryNames(probePattern, null);
       
   227                 final ObjectName expected = probe;
       
   228                 PROBE_LOG.finer("Probe res: "+res);
       
   229                 if (res.contains(expected)) {
       
   230                     throw new IOException("namespace " +
       
   231                             targetNs + " is linking to itself: " +
       
   232                             "cycle detected by probe");
       
   233                 }
       
   234             } catch (SecurityException x) {
       
   235                 PROBE_LOG.finer("Can't check for cycles: " + x);
       
   236                 // can't do anything....
       
   237             } catch (RuntimeException x) {
       
   238                 PROBE_LOG.finer("Exception raised by queryNames: " + x);
       
   239                 throw x;
       
   240             } finally {
       
   241                 probe = null;
       
   242             }
       
   243         } catch (MalformedObjectNameException x) {
       
   244             final IOException io =
       
   245                     new IOException("invalid name space: probe failed");
       
   246             io.initCause(x);
       
   247             throw io;
       
   248         }
       
   249         PROBE_LOG.fine("Probe returned - no cycles");
       
   250         probed = true;
       
   251     }
       
   252 
       
   253     // allows a Sun implementation JMX Namespace, such as the
       
   254     // JMXRemoteNamespace, to control when a probe should be sent.
       
   255     //
       
   256     // XXX: TODO this probe thing is way too complex and fragile.
       
   257     //      This *must* go away or be replaced by something simpler.
       
   258     //      ideas are welcomed.
       
   259     private boolean isProbeRequested(Object o) {
       
   260         if (o instanceof DynamicProbe)
       
   261             return ((DynamicProbe)o).isProbeRequested();
       
   262         return false;
       
   263     }
       
   264 
       
   265     /**
       
   266      * This method will send a probe to detect self-linking name spaces.
       
   267      * A self linking namespace is a namespace that links back directly
       
   268      * on itslef. Calling a method on such a name space always results
       
   269      * in an infinite loop going through:
       
   270      * [1]MBeanServer -> [2]NamespaceDispatcher -> [3]NamespaceInterceptor
       
   271      * [4]JMXNamespace -> { network // or cd // or ... } -> [5]MBeanServer
       
   272      * with exactly the same request than [1]...
       
   273      *
       
   274      * The namespace interceptor [2] tries to detect such condition the
       
   275      * *first time* that the connection is used. It does so by setting
       
   276      * a flag, and sending a queryNames() through the name space. If the
       
   277      * queryNames comes back, it knows that there's a loop.
       
   278      *
       
   279      * The DynamicProbe interface can also be used by a Sun JMXNamespace
       
   280      * implementation to request the emission of a probe at any time
       
   281      * (see JMXRemoteNamespace implementation).
       
   282      */
       
   283     private MBeanServer connection() {
       
   284         try {
       
   285             final MBeanServer c = super.source();
       
   286             if (probe != null) // should not happen
       
   287                 throw new RuntimeException("connection is being probed");
       
   288 
       
   289             if (probed == false || isProbeRequested(c)) {
       
   290                 try {
       
   291                     // Should not happen if class well behaved.
       
   292                     // Never probed. Force it.
       
   293                     //System.err.println("sending probe for " +
       
   294                     //        "target="+targetNs+", source="+srcNs);
       
   295                     sendProbe(c);
       
   296                 } catch (IOException io) {
       
   297                     throw new RuntimeException(io.getMessage(), io);
       
   298                 }
       
   299             }
       
   300 
       
   301             if (c != null) {
       
   302                 return c;
       
   303             }
       
   304         } catch (RuntimeException x) {
       
   305             throw x;
       
   306         }
       
   307         throw new NullPointerException("getMBeanServerConnection");
       
   308     }
       
   309 
       
   310 
       
   311     @Override
       
   312     protected MBeanServer source() {
       
   313         return connection();
       
   314     }
       
   315 
       
   316     @Override
       
   317     protected MBeanServer getServerForLoading() {
       
   318         // don't want to send probe on getClassLoader/getClassLoaderFor
       
   319         return super.source();
       
   320     }
       
   321 
       
   322     /**
       
   323      * Calls {@link MBeanServerConnection#queryNames queryNames}
       
   324      * on the underlying
       
   325      * {@link #getMBeanServerConnection MBeanServerConnection}.
       
   326      **/
       
   327     @Override
       
   328     public final Set<ObjectName> queryNames(ObjectName name, QueryExp query) {
       
   329         // XXX: TODO this probe thing is way too complex and fragile.
       
   330         //      This *must* go away or be replaced by something simpler.
       
   331         //      ideas are welcomed.
       
   332         PROBE_LOG.finer("probe is: "+probe+" pattern is: "+name);
       
   333         if (probe != null && isProbePattern(name)) {
       
   334             PROBE_LOG.finer("Return probe: "+probe);
       
   335             return Collections.singleton(probe);
       
   336         }
       
   337         return super.queryNames(name, query);
       
   338     }
       
   339 
       
   340     @Override
       
   341     protected ObjectName toSource(ObjectName targetName)
       
   342             throws MalformedObjectNameException {
       
   343         return proc.toSourceContext(targetName, true);
       
   344     }
       
   345 
       
   346     @Override
       
   347     protected ObjectName toTarget(ObjectName sourceName)
       
   348             throws MalformedObjectNameException {
       
   349         return proc.toTargetContext(sourceName, false);
       
   350     }
       
   351 
       
   352     //
       
   353     // Implements permission checks.
       
   354     //
       
   355     @Override
       
   356     void check(ObjectName routingName, String member, String action) {
       
   357         final SecurityManager sm = System.getSecurityManager();
       
   358         if (sm == null) return;
       
   359         if ("getDomains".equals(action)) return;
       
   360         final JMXNamespacePermission perm =
       
   361                 new  JMXNamespacePermission(serverName,member,
       
   362                 routingName,action);
       
   363         sm.checkPermission(perm);
       
   364     }
       
   365 
       
   366     @Override
       
   367     void checkCreate(ObjectName routingName, String className, String action) {
       
   368         final SecurityManager sm = System.getSecurityManager();
       
   369         if (sm == null) return;
       
   370         final JMXNamespacePermission perm =
       
   371                 new  JMXNamespacePermission(serverName,className,
       
   372                 routingName,action);
       
   373         sm.checkPermission(perm);
       
   374     }
       
   375 
       
   376     //
       
   377     // Implements permission filters for attributes...
       
   378     //
       
   379     @Override
       
   380     AttributeList checkAttributes(ObjectName routingName,
       
   381             AttributeList attributes, String action) {
       
   382         check(routingName,null,action);
       
   383         if (attributes == null || attributes.isEmpty()) return attributes;
       
   384         final SecurityManager sm = System.getSecurityManager();
       
   385         if (sm == null) return attributes;
       
   386         final AttributeList res = new AttributeList();
       
   387         for (Attribute at : attributes.asList()) {
       
   388             try {
       
   389                 check(routingName,at.getName(),action);
       
   390                 res.add(at);
       
   391             } catch (SecurityException x) { // DLS: OK
       
   392                 continue;
       
   393             }
       
   394         }
       
   395         return res;
       
   396     }
       
   397 
       
   398     //
       
   399     // Implements permission filters for attributes...
       
   400     //
       
   401     @Override
       
   402     String[] checkAttributes(ObjectName routingName, String[] attributes,
       
   403             String action) {
       
   404         check(routingName,null,action);
       
   405         if (attributes == null || attributes.length==0) return attributes;
       
   406         final SecurityManager sm = System.getSecurityManager();
       
   407         if (sm == null) return attributes;
       
   408         final List<String> res = new ArrayList<String>(attributes.length);
       
   409         for (String at : attributes) {
       
   410             try {
       
   411                 check(routingName,at,action);
       
   412                 res.add(at);
       
   413             } catch (SecurityException x) { // DLS: OK
       
   414                 continue;
       
   415             }
       
   416         }
       
   417         return res.toArray(new String[res.size()]);
       
   418     }
       
   419 
       
   420     //
       
   421     // Implements permission filters for domains...
       
   422     //
       
   423     @Override
       
   424     String[] checkDomains(String[] domains, String action) {
       
   425         // in principle, this method is never called because
       
   426         // getDomains() will never be called - since there's
       
   427         // no way that MBeanServer.getDomains() can be routed
       
   428         // to a NamespaceInterceptor.
       
   429         //
       
   430         // This is also why there's no getDomains() in a
       
   431         // JMXNamespacePermission...
       
   432         //
       
   433         return super.checkDomains(domains, action);
       
   434     }
       
   435 
       
   436     //
       
   437     // Implements permission filters for queries...
       
   438     //
       
   439     @Override
       
   440     boolean checkQuery(ObjectName routingName, String action) {
       
   441         try {
       
   442             check(routingName,null,action);
       
   443             return true;
       
   444         } catch (SecurityException x) { // DLS: OK
       
   445             return false;
       
   446         }
       
   447     }
       
   448 
       
   449 }