Skip to main content

Client context in the new JMX API

Posted by emcmanus on December 5, 2008 at 5:37 AM PST

I've mentioned in the past that one of the new features in
version 2.0 of the JMX API is "client contexts", which will allow
a client to communicate context information to a server, and a
server to adjust its behaviour accordingly.

The most obvious example is locale, where for example the
client says that it is in the French locale and the server
translates its messages and descriptions into French.

A slightly less obvious example is transaction ids. Here
the client sets up a transaction (perhaps related to a database),
performs a number of requests on the server in the context of that
transactions, then commits or aborts the transaction. Updating
configuration is the most obvious example where this is useful,
especially if the configuration is controlled by several JMX
MBeans. You want the update to have the usual transaction
properties, like atomicity and consistency, and for this to be
possible the participating MBeans must know their common
transaction id.

(I should add that we don't have explicit support for
transactions in the JMX API, but we do now have everything you
need to build the transactional solution that is appropriate for
you.)

Generalizing, a client context is
a Map<String,String>, where the keys name
context items such as locale or transaction id, with the
corresponding values. For example, the locale item is named by
the standard string "jmx.locale", so if the context
includes the French locale then the Map will have the
key "jmx.locale" with associated
value "fr".

So what does all this look like in code? Let's take the French
example to see.

Client side

Suppose the client has created a connection to the server in the
usual way:

    JMXServiceURL url = ...server address...;
    JMXConnector connector = JMXConnectorFactory.connect(url);
    MBeanServerConnection connection = connector.getMBeanServerConnection();
 

All the new stuff related to client contexts is in the
unimaginatively
named ClientContext
class. We can make a frenchConnection that is the
same as connection except that it
adds jmx.locale=fr to every request:

    MBeanServerConnection frenchConnection =
        ClientContext.withContext(connection, "jmx.locale", "fr");
 

For the particular case of jmx.locale, there is also
a special-purpose method, so another way to do it would be:

    MBeanServerConnection frenchConnection =
        ClientContext.withLocale(connection, Locale.FRENCH);
 

In either case, you can do everything
with frenchConnection that you could do
with connection, such as:

    String message = (String)
        frenchConnection.getAttribute(objectName, "Message");
 

Server side

Now suppose we want to write the MBean named
by objectName above. We want it to return a string
from its Message attribute that is translated into
the client's locale. Here's what the MBean would look like
without any exotic context stuff:

    public interface ExampleMBean {
        public String getMessage();
    }

    public class Example implements ExampleMBean {
        public String getMessage() {
            return "My hovercraft is full of eels";
        }
    }
 

Here's a simplified context-aware version:

    public class Example implements ExampleMBean {
        public String getMessage() {
            Locale locale = ClientContext.getLocale();
            String language = locale.getLanguage();
            if (language.equals(Locale.FRENCH.getLanguage()))
                return "Mon aéroglisseur est rempli d'anguilles";
            else
                return "My hovercraft is full of eels";
        }
    }
 

(In reality you would want to use ResourceBundles and the like,
rather than hard-coding translations like this.)

Like ClientContext.withLocale on the client side, on
the server side ClientContext.getLocale() is a
special-purpose version of the more general-purpose
ClientContext.getContext().
That method returns the
complete Map<String,String> that makes up the
context. So if you are using "com.example.xid" as
the context key for transaction ids, then an MBean could get the
current transaction id with:

        String xid = ClientContext.getContext().get("com.example.xid");
 

Context for local clients

A client in the same Virtual Machine as the server may have a
direct reference to the MBeanServer object. In that case it has
an alternative way of setting the context. Instead of
using withContext to get an MBeanServer object where
a context item has been set, it can
use doWithContext
to execute some code with the desired context:

    Map<String,String> newContext =
    Collections.singletonMap("jmx.locale", Locale.FRENCH.toString());
    String message =
ClientContext.doWithContext(
        newContext,
        new Callable<String>() {
        return (String)
            mbeanServer.getAttribute(objectName, "Message");
        }
    );
 

If you want to add items to the context rather than replacing it,
you can do that straightforwardly:

    Map<String,String> newContext =
    new HashMap<String,String>(ClientContext.getContext());-->  // make a copy of the context
    newContext.put("jmx.locale", Locale.FRENCH.toString());-->      // change the copy
    ...doWithContext(newContext, ...)...
 

The Map returned by ClientContext.getContext() is
unmodifiable. The only way to set it is
using doWithContext to execute some code with the new
context. As soon as that code returns, the previous context is
restored. This means that you can safely call other code without
worrying that it might change your context.

No need to care after this point

That's basically all you need to know to understand contexts.
If you're not interested in the gory details, you can stop reading
now.

How it works: the ugly truth

If we had designed contexts into the JMX Remote API from the very
beginning then this would all work in an obvious way. Each
protocol request from the client to the server would include the
context, if any. On the server side, the context would be decoded
from the request and attached to thread handling the request.

While we could have modified the JMX Remote API to work this way
in the 2.0 API, it would have posed interoperability concerns.
Contexts would only have been available if the client, server, and
connector were all running the latest version. Pre-2.0 clients
would have had no way to send contexts to servers; pre-2.0 servers
would have had no way to receive contexts from clients; and
pre-2.0 connector protocols (which is all connectors today) would
have been unable to communicate contexts.

Instead, based on an idea
by Simone Bordet, we
defined a way to encode the context of a request into the target
ObjectName of that request. We do this with a special
pseudo-namespace
called jmx.context//. When you access the
ObjectName com.example:type=Foo in the French locale,
you are actually
accessing jmx.context//jmx.locale=fr//com.example:type=Foo.
Since this looks like a namespace,
ClientContext.withContext can reuse
the narrowToNamespace
mechanism (which is similar to a shell "cd" command). Pre-2.0
clients can manually insert
the jmx.context//jmx.locale=fr// prefix. Pre-2.0
servers can install
an MBeanServerForwarder
to remove this prefix and use it to establish the thread context.
And connector protocols do not need to be modified in any way, so
all existing connectors support contexts.

I called jmx.context// a pseudo-namespace because
even though it looks like one it is not actually implemented as
one. There are a few reasons for this, but basically it boils
down to a question of ordering with respect to other things you
might want to add in the path between the client and the server.
You might want to add
an MBeanServerForwarder
to localize the descriptions in
the MBeanInfo
returned
by getMBeanInfo,
but for this to work the locale would already need to be set when
this localizer intercepted the getMBeanInfo call.
(In fact, the new API includes such
a forwarder.)
Or, you might already have an MBeanServerForwarder
that performs security checks based on the ObjectName
in the requests it sees; but if you now start
adding jmx.context// into
that ObjectName you'll have to change your checking
logic.

For these reasons, we simulate
a jmx.context// namespace in a
special MBeanServerForwarder that is deployed by
default in the RMI connector and can be deployed in any other
connector. This works well in practice, and in particular solves
the problems described just above. The only drawback is that a
local client of the MBean Server does not see
the jmx.context// namespace by default. This usually
does not matter, because a local client can
use ClientContext.doWithContext, which will work.
Client code that sometimes needs to operate with a
remote MBeanServerConnection and sometimes with a
local MBeanServer should be rare. When it does
arise, the solution is to wrap the local MBeanServer
inside
the context
namespace forwarder
. Since an MBeanServerForwarder
is-an MBeanServer, you can just do this once and use
the wrapped MBeanServer everywhere you used the
original MBeanServer before.

French locale encoded into ObjectName and decoded in MBeanServerForwarder src="http://weblogs.java.net/blog/emcmanus/archive/frenchconnection.png/context.png"
width="622" height="575" />

[Tags:
]

Related Topics >>

Comments

using Client Context for retrieving time indexed data

I was wondering if using Client Context would be an appropriate way of retrieving historical data. With the current JMX API, one can only retrieve the most current value of the data. Even if the JMX agent keeps an history of the data values at different times, the only way to retrieve them is to pass the date/time as a parameter on a JMX operation, i.e. it cannot be done with a (read-only) attribute. One consequence is that a JMX client (like jconsole) can only draw a time graph that start at the time the client is started. Having a way to query for past values (if such values were kept by the agent) would permit to draw a better time graph. So the idea would be to pass the queryied data/time in a Client Context. I see two problems with this: 1) JMX 2.0 was postponed and 2) The Client Context does not seem to be able to return values in the opposite direction, which would be needed to return the actual date/time of the sample returned. Is there another way to achieve the same goals?

ecki, that's a very interesting question! I think the answer is no, because in general you cannot trust a client to be telling the truth about its authorized identity. You need to have some kind of authentication mechanism between the client and the server. The JMX Remote API already has provision for "subject delegation". The idea is that you can say that if the client has authenticated with a certain identity then it is allowed to claim certain other identities. So you can set up an authenticated connection as TrustedClient and then do operations on behalf of IdentityA and IdentityB. See JMXConnector.getMBeanServerConnection(Subject) and SubjectDelegationPermission. By the way this is the nearest thing we have to client contexts today, and people have sometimes (ab)used it to communicate things like transaction IDs.

Is the authorisation context (subject, user, role,...) also something which should be propagated this way. I am not sure if JAAS in connection to JMX is somewhere defined? Session and transaction IDs are really usefull. If only there would be a way to actually access a TX id in a JavaEE CMT component...

Hello Eamonn, how are you doing? Got quiet here on the Blog. I have a somewhat (to the article) unrelated point to discuss. Do you know if any work on the platform management side is going on? The current MemoryPool, MemoryManager and GarbageCollection PlatformMBeans fall short in monitoring application performance. For example it is hard to track the serial GC if you have lots of allocation failures in CMS. It is also hard to support the new G1 metrics. It is also not so easy to measure object allocation rates or distinguish between concurrent and stop-the-world times. Even jstat fails to deliver that, and you would have to parse it from verbose GC logs. Its really a think some JMX counters could greatly improve. Greetings Bernd

Eamon, http://weblogs.java.net/blog/emcmanus/archive/2007/02/index.html We consider use JMX for monitoring complex distributed application that runs on multiple hosts and seems like using mirrors would greatly simplify our management application. MBeanServerDelegate could be used to synchronize lifetime of mirror and mirrored beans. We are trying to decide if we want to use Relation Service to describe relationships between beans or not. We can use mirror for Relation Service beans of sub-agents, assuming that relations may be established only between beans that are from the same JVM. But this would be rather strong restriction. Or we can try to replicate relations from Relation Service of sub-agent to Relation Service of an agent, using notifications of relation service. What would be you recommendation for using relation service and presenting relations from sub-agents on master agent, considering that we have to use JDK 1.5? Thanks a lot, Viktor