RoutingProxy.java 16.4 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
/*
 * 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.logging.Level;
import java.util.logging.Logger;

33
import javax.management.InstanceNotFoundException;
34 35 36 37 38 39 40 41 42 43
import javax.management.MBeanException;
import javax.management.MBeanRegistrationException;

import javax.management.MBeanServerConnection;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;
import javax.management.namespace.JMXNamespaces;


/**
44
 * A RoutingProxy narrows on a given name space in a
45 46 47 48
 * source object implementing MBeanServerConnection.
 * It is used to implement
 * {@code JMXNamespaces.narrowToNamespace(...)}.
 * This abstract class has two concrete subclasses:
49 50 51
 * <p>{@link RoutingConnectionProxy}: to narrow down into an
 *    MBeanServerConnection.</p>
 * <p>{@link RoutingServerProxy}: to narrow down into an MBeanServer.</p>
52 53 54 55 56 57 58 59 60 61 62 63 64
 *
 * <p>This class can also be used to "broaden" from a namespace.  The same
 * class is used for both purposes because in both cases all that happens
 * is that ObjectNames are rewritten in one way on the way in (e.g. the
 * parameter of getMBeanInfo) and another way on the way out (e.g. the
 * return value of queryNames).</p>
 *
 * <p>Specifically, if you narrow into "a//" then you want to add the
 * "a//" prefix to ObjectNames on the way in and subtract it on the way
 * out.  But ClientContext uses this class to subtract the
 * "jmx.context//foo=bar//" prefix on the way in and add it back on the
 * way out.</p>
 *
65 66 67 68 69
 * <p><b>
 * This API is a Sun internal API and is subject to changes without notice.
 * </b></p>
 * @since 1.7
 */
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
//
// RoutingProxies are client side objects which are used to narrow down
// into a namespace. They are used to perform ObjectName translation,
// adding the namespace to the routing ObjectName before sending it over
// to the source connection, and removing that prefix from results of
// queries, createMBean, registerMBean, and getObjectInstance.
// This translation is the opposite to that which is performed by
// NamespaceInterceptors.
//
// There is however a special case where routing proxies are used on the
// 'server' side to remove a namespace - rather than to add it:
// This the case of ClientContext.
// When an ObjectName like "jmx.context//c1=v1,c2=v2//D:k=v" reaches the
// jmx.context namespace, a routing proxy is used to remove the prefix
// c1=v1,c2=v2// from the routing objectname.
//
// For a RoutingProxy used in a narrowDownToNamespace operation, we have:
//     targetNs="" // targetNS is the namespace 'to remove'
//     sourceNS=<namespace-we-narrow-down-to> // namespace 'to add'
//
// For a RoutingProxy used in a ClientContext operation, we have:
//     targetNs=<encoded-context> // context must be removed from object name
//     sourceNs="" // nothing to add...
//
// Finally, in order to avoid too many layers of wrapping,
// RoutingConnectionProxy and RoutingServerProxy can be created through a
96
// factory method that can concatenate namespace paths in order to
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
// return a single RoutingProxy - rather than wrapping a RoutingProxy inside
// another RoutingProxy. See RoutingConnectionProxy.cd and
// RoutingServerProxy.cd
//
// The class hierarchy is as follows:
//
//                           RoutingMBeanServerConnection
//                   [abstract class for all routing interceptors,
//                    such as RoutingProxies and HandlerInterceptors]
//                            /                          \
//                           /                            \
//                    RoutingProxy                HandlerInterceptor
//          [base class for                   [base class for server side
//           client-side objects used          objects, created by
//           in narrowDownTo]                  DispatchInterceptors]
//           /                  \                   |          \
//  RoutingConnectionProxy       \                  |      NamespaceInterceptor
//  [wraps MBeanServerConnection  \                 |     [used to remove
//   objects]                      \                |      namespace prefix and
//                        RoutingServerProxy        |      wrap  JMXNamespace]
//                        [wraps MBeanServer        |
//                         Objects]                 |
//                                            DomainInterceptor
//                                            [used to wrap JMXDomain]
//
// RoutingProxies also differ from HandlerInterceptors in that they transform
// calls to MBeanServerConnection operations that do not have any parameters
// into a call to the underlying JMXNamespace MBean.
// So for instance a call to:
//    JMXNamespaces.narrowDownToNamespace(conn,"foo").getDomains()
// is transformed into
//    conn.getAttribute("foo//type=JMXNamespace","Domains");
//
130 131 132 133 134 135 136 137 138 139 140 141
public abstract class RoutingProxy<T extends MBeanServerConnection>
        extends RoutingMBeanServerConnection<T> {

    /**
     * A logger for this class.
     **/
    private static final Logger LOG = JmxProperties.NAMESPACE_LOGGER;

    // The source MBeanServerConnection
    private final T source;

    // The name space we're narrowing to (usually some name space in
142 143
    // the source MBeanServerConnection), e.g. "a" for the namespace
    // "a//".  This is empty in the case of ClientContext described above.
144 145
    private final String                sourceNs;

146 147 148
    // The name space we pretend to be mounted in.  This is empty except
    // in the case of ClientContext described above (where it will be
    // something like "jmx.context//foo=bar".
149 150 151 152 153 154 155 156 157 158 159
    private final String                targetNs;

    // The name of the JMXNamespace that handles the source name space
    private final ObjectName            handlerName;
    private final ObjectNameRouter      router;
    private volatile String             defaultDomain = null;

    /**
     * Creates a new instance of RoutingProxy
     */
    protected RoutingProxy(T source,
160 161 162
                           String sourceNs,
                           String targetNs,
                           boolean probe) {
163 164 165 166 167 168 169 170 171 172 173 174
        if (source == null) throw new IllegalArgumentException("null");
        this.sourceNs = JMXNamespaces.normalizeNamespaceName(sourceNs);

        // Usually sourceNs is not null, except when implementing
        // Client Contexts
        //
        if (sourceNs.equals("")) {
            this.handlerName = null;
        } else {
            // System.err.println("sourceNs: "+sourceNs);
            this.handlerName =
                JMXNamespaces.getNamespaceObjectName(this.sourceNs);
175 176 177 178 179 180 181 182 183 184 185
            if (probe) {
                try {
                    if (!source.isRegistered(handlerName)) {
                        InstanceNotFoundException infe =
                                new InstanceNotFoundException(handlerName);
                        throw new IllegalArgumentException(sourceNs +
                                ": no such name space", infe);
                    }
                } catch (IOException x) {
                    throw new IllegalArgumentException("source stale: "+x,x);
                }
186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201
            }
        }
        this.source = source;
        this.targetNs = (targetNs==null?"":
            JMXNamespaces.normalizeNamespaceName(targetNs));
        this.router =
                new ObjectNameRouter(this.targetNs,this.sourceNs);

        if (LOG.isLoggable(Level.FINER))
            LOG.finer("RoutingProxy for " + this.sourceNs + " created");
    }

    @Override
    public T source() { return source; }

    @Override
202
    public ObjectName toSource(ObjectName targetName) {
203 204 205 206 207 208 209 210 211 212 213
        if (targetName == null) return null;
        if (targetName.getDomain().equals("") && targetNs.equals("")) {
            try {
                if (defaultDomain == null)
                    defaultDomain = getDefaultDomain();
            } catch(Exception x) {
                LOG.log(Level.FINEST,"Failed to get default domain",x);
            }
            if (defaultDomain != null)
                targetName = targetName.withDomain(defaultDomain);
        }
214
        return router.toSourceContext(targetName,true);
215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230
    }

    @Override
    protected ObjectName newSourceMBeanName(ObjectName targetName)
        throws MBeanRegistrationException {
        if (targetName != null) return super.newSourceMBeanName(targetName);

        // OK => we can accept null if sourceNs is empty.
        if (sourceNs.equals("")) return null;

        throw new MBeanRegistrationException(
                new IllegalArgumentException(
                "Can't use null ObjectName with namespaces"));
    }

    @Override
231
    public ObjectName toTarget(ObjectName sourceName) {
232
        if (sourceName == null) return null;
233
        return router.toTargetContext(sourceName,false);
234 235 236 237 238 239 240 241 242 243 244 245 246
    }

    private Object getAttributeFromHandler(String attributeName)
            throws IOException {

        try {
            return source().getAttribute(handlerName,attributeName);
         } catch (RuntimeException ex) {
            throw makeCompliantRuntimeException(ex);
         } catch (IOException x) {
             throw x;
         } catch (MBeanException ex) {
             throw new IOException("Failed to get "+attributeName+": "+
247 248
                     ex.getCause(),
                     ex.getCause());
249
         } catch (Exception ex) {
250
             throw new IOException("Failed to get "+attributeName+": "+
251
                     ex,ex);
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
         }
    }

    // We cannot call getMBeanCount() on the underlying
    // MBeanServerConnection, because it would return the number of
    // 'top-level' MBeans, not the number of MBeans in the name space
    // we are narrowing to. Instead we're calling getMBeanCount() on
    // the JMXNamespace that handles the source name space.
    //
    // There is however one particular case when the sourceNs is empty.
    // In that case, there's no handler - and the 'source' is the top
    // level namespace. In that particular case, handlerName will be null,
    // and we directly invoke the top level source().
    // This later complex case is only used when implementing ClientContexts.
    //
    @Override
    public Integer getMBeanCount() throws IOException {
        try {
            if (handlerName == null) return source().getMBeanCount();
            return (Integer) getAttributeFromHandler("MBeanCount");
         } catch (RuntimeException ex) {
            throw makeCompliantRuntimeException(ex);
         }
    }

    // We cannot call getDomains() on the underlying
    // MBeanServerConnection, because it would return the domains of
    // 'top-level' MBeans, not the domains of MBeans in the name space
    // we are narrowing to. Instead we're calling getDomains() on
    // the JMXNamespace that handles the source name space.
    //
    // There is however one particular case when the sourceNs is empty.
    // In that case, there's no handler - and the 'source' is the top
    // level namespace. In that particular case, handlerName will be null,
    // and we directly invoke the top level source().
    // This later complex case is only used when implementing ClientContexts.
    //
    @Override
    public String[] getDomains() throws IOException {
        try {
            if (handlerName == null) return source().getDomains();
            return (String[]) getAttributeFromHandler("Domains");
        } catch (RuntimeException ex) {
            throw makeCompliantRuntimeException(ex);
        }
    }

    // We cannot call getDefaultDomain() on the underlying
    // MBeanServerConnection, because it would return the default domain of
    // 'top-level' namespace, not the default domain in the name space
    // we are narrowing to. Instead we're calling getDefaultDomain() on
    // the JMXNamespace that handles the source name space.
    //
    // There is however one particular case when the sourceNs is empty.
    // In that case, there's no handler - and the 'source' is the top
    // level namespace. In that particular case, handlerName will be null,
    // and we directly invoke the top level source().
    // This later complex case is only used when implementing ClientContexts.
    //
    @Override
    public String getDefaultDomain() throws IOException {
        try {
            if (handlerName == null) {
                defaultDomain = source().getDefaultDomain();
            } else {
                defaultDomain =(String)
                        getAttributeFromHandler("DefaultDomain");
            }
            return defaultDomain;
        } catch (RuntimeException ex) {
            throw makeCompliantRuntimeException(ex);
        }
    }

    public String getSourceNamespace() {
        return sourceNs;
    }

    public String getTargetNamespace() {
        return targetNs;
    }

    @Override
    public String toString() {
        return super.toString()+", sourceNs="+
                sourceNs + (targetNs.equals("")?"":
                    (" mounted on targetNs="+targetNs));
    }

341 342 343 344 345
    // Creates an instance of a subclass 'R' of RoutingProxy<T>
    // RoutingServerProxy and RoutingConnectionProxy have their own factory
    // instance.
    static interface RoutingProxyFactory<T extends MBeanServerConnection,
            R extends RoutingProxy<T>> {
346 347
            public R newInstance(
                    T source, String sourcePath, String targetPath, boolean probe);
348 349 350 351 352 353 354 355 356 357 358 359 360 361 362
    }

    // Performs a narrowDownToNamespace operation.
    // This method will attempt to merge two RoutingProxies in a single
    // one if they are of the same class.
    //
    // This method is never called directly - it should be called only by
    // subclasses of RoutingProxy.
    //
    // As for now it is called by:
    // RoutingServerProxy.cd and RoutingConnectionProxy.cd.
    //
    static <T extends MBeanServerConnection, R extends RoutingProxy<T>>
           R cd(Class<R> routingProxyClass,
              RoutingProxyFactory<T,R> factory,
363
              T source, String sourcePath, boolean probe) {
364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385
        if (source == null) throw new IllegalArgumentException("null");
        if (source.getClass().equals(routingProxyClass)) {
            // cast is OK here, but findbugs complains unless we use class.cast
            final R other = routingProxyClass.cast(source);
            final String target = other.getTargetNamespace();

            // Avoid multiple layers of serialization.
            //
            // We construct a new proxy from the original source instead of
            // stacking a new proxy on top of the old one.
            // - that is we replace
            //      cd ( cd ( x, dir1), dir2);
            // by
            //      cd (x, dir1//dir2);
            //
            // We can do this only when the source class is exactly
            //    RoutingServerProxy.
            //
            if (target == null || target.equals("")) {
                final String path =
                    JMXNamespaces.concat(other.getSourceNamespace(),
                    sourcePath);
386
                return factory.newInstance(other.source(), path, "", probe);
387 388 389 390 391 392
            }
            // Note: we could do possibly something here - but it would involve
            //       removing part of targetDir, and possibly adding
            //       something to sourcePath.
            //       Too complex to bother! => simply default to stacking...
        }
393
        return factory.newInstance(source, sourcePath, "", probe);
394
    }
395
}