blob: da85fc6d4ae739eb758150082a28646bd82391a9 [file] [log] [blame]
/*
* 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;
}
}
}