The Source for Java Technology Collaboration
User: Password:
Register | Login help    

Search

Online Books:
java.net on MarkMail:


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

[Tags: ]

Related Topics >> Open JDK      
Comments
Comments are listed in date ascending order (oldest first)

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

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

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...

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.