aboutsummaryrefslogtreecommitdiff
path: root/src/share/classes/com/sun/jmx/namespace/RoutingProxy.java
blob: aa35c5bdaed56754d2591ed463b6632dc3a6f533 (plain)
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
/*
 * 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;

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;


/**
 * A RoutingProxy narrows on a given name space in a
 * source object implementing MBeanServerConnection.
 * It is used to implement
 * {@code JMXNamespaces.narrowToNamespace(...)}.
 * This abstract class has two concrete subclasses:
 * <p>{@link RoutingConnectionProxy}: to narrow down into an
 *    MBeanServerConnection.</p>
 * <p>{@link RoutingServerProxy}: to narrow down into an MBeanServer.</p>
 *
 * <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>
 *
 * <p><b>
 * This API is a Sun internal API and is subject to changes without notice.
 * </b></p>
 * @since 1.7
 */
//
// 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...
//
// RoutingProxies can also be used on the client side to implement
// "withClientContext" operations. In that case, the boolean parameter
// 'forwards context' is set to true, targetNs is "", and sourceNS may
// also be "". When forwardsContext is true, the RoutingProxy dynamically
// creates an ObjectNameRouter for each operation - in order to dynamically add
// the context attached to the thread to the routing ObjectName. This is
// performed in the getObjectNameRouter() method.
//
// Finally, in order to avoid too many layers of wrapping,
// RoutingConnectionProxy and RoutingServerProxy can be created through a
// factory method that can concatenate namespace pathes in order to
// 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");
//
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
    // the source MBeanServerConnection
    private final String                sourceNs;

    // The name space we pretend to be mounted in (usually "")
    private final String                targetNs;

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

    /**
     * Creates a new instance of RoutingProxy
     */
    protected RoutingProxy(T source,
                          String sourceNs,
                          String targetNs,
                          boolean forwardsContext) {
        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);
            try {
                // System.err.println("handlerName: "+handlerName);
                if (!source.isRegistered(handlerName))
                    throw new IllegalArgumentException(sourceNs +
                            ": no such name space");
            } catch (IOException x) {
                throw new IllegalArgumentException("source stale: "+x,x);
            }
        }
        this.source = source;
        this.targetNs = (targetNs==null?"":
            JMXNamespaces.normalizeNamespaceName(targetNs));
        this.router =
                new ObjectNameRouter(this.targetNs,this.sourceNs);
        this.forwardsContext = forwardsContext;

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

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

    ObjectNameRouter getObjectNameRouter() {
// TODO: uncomment this when contexts are added
//        if (forwardsContext)
//            return ObjectNameRouter.wrapWithContext(router);
//        else
            return router;
    }

    @Override
    public ObjectName toSource(ObjectName targetName)
        throws MalformedObjectNameException {
        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);
        }
        final ObjectNameRouter r = getObjectNameRouter();
        return r.toSourceContext(targetName,true);
    }

    @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
    public ObjectName toTarget(ObjectName sourceName)
        throws MalformedObjectNameException {
        if (sourceName == null) return null;
        final ObjectNameRouter r = getObjectNameRouter();
        return r.toTargetContext(sourceName,false);
    }

    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+": "+
                     ex.getCause(),
                     ex.getCause());
         } catch (Exception ex) {
             throw new IOException("Failed to get "+attributeName+": "+
                     ex,ex);
         }
    }

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

    // 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>> {
            R newInstance(T source,
                    String sourcePath, String targetPath,
                    boolean forwardsContext);
            R newInstance(T source,
                    String sourcePath);
    }

    // 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,
              T source, String sourcePath) {
        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);
                return factory.newInstance(other.source(),path,"",
                                           other.forwardsContext);
            }
            // 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...
        }
        return factory.newInstance(source,sourcePath);
    }
}