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