Client context in the new JMX API
Posted by emcmanus on December 05, 2008 at 05:37 AM | Comments (4)
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.
[Tags: jmx
jsr255]
Bookmark blog post: del.icio.us Digg DZone Furl Reddit
Comments
Comments are listed in date ascending order (oldest first) | Post Comment
-
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...
Posted by: ecki on December 07, 2008 at 09:16 PM
-
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.
Posted by: emcmanus on December 08, 2008 at 01:34 AM
-
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
Posted by: vlioutyi_i2 on January 30, 2009 at 03:11 PM
-
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
Posted by: b_eckenfels on April 27, 2009 at 03:39 PM
|