Table of Contents
The following phrases all describe the same thing.
I recommend that you use Java 1.4 or later if you will be running a JMXConnector. If you run into problems running connectors with 1.3, I don't consider that my problem.
A Remote JMX Client is just like a Local JMX Client except that it must instantiate a Connector client and connect it up to a Connector server to get a MBeanServerConnection. It then uses the MBeanServerConnection in the same way as a Local JMX Client. Remote JMX applications can not work with an MBeanServer object, only an MBeanServerConnection.
The Agent must run a Connector Server using the same protocol that you will use on the client side. If your server serves tranport "rmi", then your client(s) need to use protocol "rmi".
RMI and JMXMP URLs
|
Sample JMXMP Server URL: service:jmx:jmxmp://0.0.0.0:2004 |
This simply specifies to have the JMXConnectorServer listen to
port 2004 using the JMXMP protocol.
|
|||
|
Sample RMI Server URL: service:jmx:rmi:///jndi/rmi://localhost:2003/jmxdemo |
The rmi:// part specifies
that the JMX server will use the RMI protocol, and instead
of running its own listener, it will advertize on the RMI
server at the specified location.
The given host/ip address and port identify the end-point
at which to reach an RMI server which is already running.
The jmxdemo part specifies that the this
program will register this service with the key
jmxdemo on the RMI server.
|
|||
|
Sample JMXMP Client URL: service:jmx:jmxmp://localhost:2004 | This is as straight-forward as you can get. The client will attempt to connect to a JMXConnectorServer on port 2004 on localhost and will speak JMXMP protocol. | |||
|
Sample RMI Client URL: service:jmx:rmi:///jndi/rmi://localhost:2003/jmxdemo |
The rmi:// part specifies to connect to
the RMI server at the specificied host and port, and look for
the service with the given JNDI name, jmxdemo
in this case.
It will then use the JMX RMI protocol to speak to the
JMXConnectorServer which registered that service.
|
I definitley do not recommend that you use an RMI connector (for
reasons discussed below). Nevertheless, most of our examples
will use RMI because any self-respecting JMX developer should be
converstant with RMI connectors. If you're not going to use an
RMI connector, you should at least understand why.
The only real-word reason to use RMI, in my opinion, is that
you have so many JMX servers which are so volatile that you
need dynamic service lookup; or that you are prohibited from
using any class outside of the base J2SE (even if it is made
by Sun).
If you use any protocol other than RMI, you will need to
add some jar to your classpath, since J2SE does not contain
classes implementing any other protocol than RMI.
For JMXMP, this simply means adding
jmxremote_optional.jar from Sun's JMX Remote
RI (available
here).
Here's a simple remote client (also available at ConnectorClient.java). It justs displays information about the specified MBean, but you can copy and paste the relevant portions to make any application of yours a JMX client; or use this as a starting point. (I'll present a Connector Server shortly). As you can see, there is very little code other than argument parsing and TLS setup (ignore the TLS part for now, if you are just learning about Distributed Services and Connectors right now).
Example 8.1. ConnectorClient.java
/*
* @(#)$Id: ConnectorClient.java 2011 2009-01-09 17:07:43Z blaine $
*
* Copyright 2004-2009 Axis Data Management Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import javax.management.remote.JMXConnector;
import javax.management.MBeanServerConnection;
import javax.management.ObjectName;
import javax.management.remote.JMXConnectorFactory;
import javax.management.remote.JMXServiceURL;
import javax.management.JMException;
import java.util.Map;
import java.util.HashMap;
import java.io.IOException;
import javax.management.remote.JMXAuthenticator;
/**
* Dumps info about the specified MBean from the JMXConnectorServer at
* the specified URL.
*/
public class ConnectorClient {
static MBeanServerConnection mbsc = null;
static public void main(String[] sa) throws IOException, JMException {
boolean tlsMode = false;
String urlString = null;
String beanId = null;
/* Command-line argument parsing */
if (sa.length == 3 && sa[0].equals("--tls")) {
tlsMode = true;
urlString = sa[1];
beanId = sa[2];
} else if (sa.length == 2) {
urlString = sa[0];
beanId = sa[1];
}
if (urlString == null) {
System.err.println(
"SYNTAX: java [--tls] ConnectorClient JMXServiceURL beanspec\n"
+ "Sample invocations:\n "
+ "service:jmx:rmi:///jndi/rmi://saturn.acme.com:9999/jndi_id\n"
+ " service:jmx:jmxmp://neptune.acme.com:9999\n"
+ "(A JMXMP impl. required in classpath for jmxmp service.\n"
+ "Try Sun's free jmxremote_optional.jar).");
System.exit(2);
}
Map env = new HashMap();
if (tlsMode) {
// TLS Mode setup.
// Note that the settings below are conditional, so you can
// override then with "java -Djavax...=Y... ConnectorClient..."
// It's definitely not safe to use -D to set passwords, though,
// but it's useful for prototyping.
if (System.getProperty("javax.net.ssl.trustStore") == null)
System.setProperty("javax.net.ssl.trustStore",
"server-cert.store");
if (System.getProperty("javax.net.ssl.keyStorePassword") == null)
System.setProperty("javax.net.ssl.keyStorePassword",
"pwdC1store");
// Comment out the lines above if the server isn't requiring
// a cert for your client.
if (System.getProperty("javax.net.ssl.keyStore") == null)
System.setProperty("javax.net.ssl.keyStore", "client1.store");
/* The method above is the simplest (IMO therefore the best)
* method if your application doesn't use certs for any other
* purpose. You should use the instance-based TLS configuration
* method if your app uses certs for any other purpose in a
* single instantiation (i.e., it could fetch web pages over
* https, or be a TLS Soap client, etc., or it could run connect
* to multiple TLS JMXConnectorServers).
*
* The instance-based method allocates a SSLSocketFactory
* based on the SSLContext instance which you instantiate and
* configure, so you can configure multiple SSLSocketFactories
* with different SSLContext instances. This all applies to
* any standard JSSE TLS application, but for JMX, you
* associate the allocated SSLSocketFactory to the Connector
* with:
*
* env.put("jmx.remote.tls.socket.factory", yourFactory);
*/
env.put("jmx.remote.profiles", "TLS");
//env.put("jmx.remote.tls.enabled.protocols", "TLSv1");
//env.put("jmx.remote.tls.enabled.cipher.suites",
//"SSL_RSA_WITH_NULL_MD5");
// Most users will probably want to use the default TLS
// protocols and suites.
}
JMXConnector c =
JMXConnectorFactory.connect(new JMXServiceURL(urlString), env);
// If you aren't setting a profile or any other options, you can use
// null for the second connect() parameter, instead of an empty list.
try {
mbsc = c.getMBeanServerConnection();
// For this example, I chose to not expose the Adaptor as an
// MBean, which is sometimes a good thing to do for security.
// Therefore, I use it as a normal Java Object.
System.err.println("Info on '" + beanId + "' is:");
javax.management.MBeanAttributeInfo[] aa =
mbsc.getMBeanInfo(new ObjectName(beanId)).getAttributes();
for (int i = 0; i < aa.length; i++)
System.err.println(aa[i].getName());
} finally {
if (c != null) c.close();
}
System.exit(0);
}
}
The RMI protocol is required by all JMX Remote implementations, but it requires use of a running rmi registry server, and indirection (most simply by JNDI).
If you start an JMXConnectorServer with an rmi URL, you will need to start an rmi registry server if one is not already running on the host and port specified at the end of your URL. This is easily done by running
rmiregistry 2003
Where 2003 is the port (by default it'll serve all the host addresses on the server). The program rmiregistry comes with Java (Sun's JDKs, at least).
For some reason, Sun put a lot of effort into adding an extra level of indirection into the RMI method. Besides the extra configuration necessary, you actually have to run extra processes for clients to locate your RMI servers. Instead of specifying a URL that points right to your JMX service, your clients must use a lookup service like JNDI, SLP, etc. and JMX access must be made through the lookup service. Most people will never work with a JMX infrastructure large enough to justify the complexity needed for this indirection, but you will need to understand it nonetheless. (We call this phenomenon over-engineering).
In contrast to RMI, Sun's generic JMXMP Connector is simple and
intuitive.
Just compare the rmi URL and the jmxmp URL above.
The JMXMP Connector doesn't come with J2SE, but all you have to
do is download the latest JMX Remote RI from
http://java.sun.com/products/JavaManagement/download.html,
pull the file jmxremote_optional.jar from
the distribution, and put it in the classpath of your server and
client classes.
This JMXMP Connector
just routes serialized Java objects over a TCP connection.
There are two great things about jmxmp.
Both server and client just specify the server address and
port-- no extra server (like JNDI) is needed.
Another great benefit is, JMXMP supports TLS in an intuitive and
powerful way, as covered in the
TLS chapter.
Now for the server side of the Connector.
Just like most JMXConnectorServer programs, ours controls
access to an MBeanServer and is therefore a JMX Agent,
hence I've named this program
ConnectorServerAgent.
This is the agent that is used by the demo site web app at
http://admc.com/jmxhtml/jmx
(where it runs under the Java security manager).
You can download this source code from
ConnectorServerAgent.java.
If you run this class as-is, it will require the classs
SampleStd.class and
SampleStdMBean.class to be in your
runtime classpath.
As the JavaDocs in the code explain, you can subclass
ConnectorServerAgent to serve any
MBeans that you want to.
If you want to play with the rest of the sample MBeans
from this Howto, or any of your own MBeans which use
AbstractDynamicMBean, you'll also
need the AbstractDynamicMBean classes
in your classpath.
(Probably easiest to just put
http://admc.com/dist/admcjmx-adaptors.jar
into the classpath).
The length of this program is only due to TLS support and to
make it scalable (so you can easily use it for your own
purposes).
Example 8.2. ConnectorServerAgent.java
/*
* @(#)$Id: ConnectorServerAgent.java 2011 2009-01-09 17:07:43Z blaine $
*
* Copyright 2004-2009 Axis Data Management Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import javax.management.MBeanServerFactory;
import javax.management.MBeanServer;
import javax.management.ObjectName;
import javax.management.remote.JMXConnectorServerFactory;
import javax.management.remote.JMXServiceURL;
import javax.management.JMException;
import java.util.HashMap;
import java.util.Map;
import javax.management.ReflectionException;
import java.io.File;
/**
* Sample Agent that runs a JMXConnectorServer using the specified URL.
*
* Subclass and override method loadBeans() to intialize with your own
* MBeans (or none); and override isRequireClientAuth() if you want to
* run with TLS but without Client certs.
*
* It's useful for debugging (and learning) to be able to run this program
* in the foreground. If you don't ever want it to run in the foreground,
* then override serve() to run this.serve() in a Thread (or similar).
*/
public class ConnectorServerAgent {
private boolean tlsMode = false;
private String urlString = null;
private Map env = new HashMap();
static public void main(String[] sa)
throws IOException, JMException {
boolean tlsMode = false;
String urlString = null;
/* Command-line argument parsing */
if (sa.length == 2 && sa[0].equals("--tls")) {
tlsMode = true;
urlString = sa[1];
} else if (sa.length == 1) {
urlString = sa[0];
}
if (urlString == null) {
System.err.println(
"SYNTAX: java [--tls] ConnectorServerAgent JMXServiceURL\n"
+ "JMXServiceURL examples:\n "
+ "service:jmx:rmi:///jndi/rmi://localhost:9999/jndi_id\n"
+ " service:jmx:jmxmp://0.0.0.0:9999\n"
+ "(A JMXMP impl. required in classpath for jmxmp service.\n"
+ "Try Sun's free jmxremote_optional.jar).\n"
+ "(RMI URLs require an RMI registry to be running at the "
+ "specified address/port).");
System.exit(2);
}
new ConnectorServerAgent(urlString, tlsMode).serve();
}
protected ConnectorServerAgent(String urlString, boolean tlsMode) {
this.urlString = urlString;
this.tlsMode = tlsMode;
}
/**
* Override this class to load your own Beans upon startup, or override
* with a no-op method to load no beans upon startup (in which case
* clients will need to add Beans).
*/
protected void loadBeans(MBeanServer beanServer) throws JMException {
try {
beanServer.createMBean("tst.SampleStd", new ObjectName("a:b=c"));
} catch (ReflectionException re) {
throw new JMException(
"The sample MBean class 'tst.SampleStd' is not in your classpath.\n"
+ "Either fix your classpath, or subclass "
+ getClass().getName() + '.');
}
}
/**
* Initializes the Agent and runs the JMXServerConnector.
*/
protected void serve() throws JMException, IOException {
if (tlsMode) setupTls();
MBeanServer mBeanServer = MBeanServerFactory.createMBeanServer();
loadBeans(mBeanServer);
(JMXConnectorServerFactory.newJMXConnectorServer(
new JMXServiceURL(urlString), env, mBeanServer)).start();
System.out.println(
"If you're running this server in the foreground, you "
+ "can stop it with Ctrl-C.");
}
/**
* TLS Mode setup.
*/
protected void setupTls() {
// Note that the settings below are conditional, so you can
// override then with "java -Djavax...=Y... ConnectorServerAgent..."
// It's definitely not safe to use -D to set passwords, though,
// but it's useful for prototyping.
if (System.getProperty("javax.net.ssl.trustStore") == null)
System.setProperty("javax.net.ssl.trustStore",
"client1-cert.store");
if (System.getProperty("javax.net.ssl.keyStorePassword") == null)
System.setProperty("javax.net.ssl.keyStorePassword",
"pwdSstore");
if (System.getProperty("javax.net.ssl.keyStore") == null)
System.setProperty("javax.net.ssl.keyStore", "server.store");
/* The method above is the simplest (IMO therefore the best)
* method if your application doesn't use certs for any other
* purpose. You should use the instance-based TLS configuration
* method if your app uses certs for any other purpose
* (i.e., it could fetch web pages over https, or be a TLS
* Soap client, etc., or it could run multiple TLS
* JMXConnectorServers).
*
* The instance-based method allocates a SSLSocketFactory
* based on the SSLContext instance which you instantiate and
* configure, so you can configure multiple SSLSocketFactories
* with different SSLContext instances. This all applies to
* any standard JSSE TLS application, but for JMX, you
* associate the allocated SSLSocketFactory to the Connector
* with:
*
* env.put("jmx.remote.tls.socket.factory", yourFactory);
*/
env.put("jmx.remote.profiles", "TLS");
//env.put("jmx.remote.tls.enabled.protocols", "TLSv1");
//env.put("jmx.remote.tls.enabled.cipher.suites",
//"SSL_RSA_WITH_NULL_MD5");
// Most users will probably want to use the default TLS
// protocols and suites.
env.put("jmx.remote.tls.need.client.authentication",
Boolean.toString(isRequireClientAuth()));
// Comment out the line above if you don't want to require
// clients to have their own certs.
if ((new File("access.properties")).isFile())
env.put("jmx.remote.x.access.file", "access.properties");
// IF file "access.properties" is present in $PWD (from where server
// is started), it must have keys of permitted client cert subjects,
// and values of "readwrite" or "readonly".
// N.b. You MUST ESCAPE all spaces, colons, and equal signes in the
// subject with backslashes!
// Example record:
// CN\=proto\ client\ 1,OU\=RND,O\=Fake\ Corp.,C\=US readwrite
}
/**
* Only used if running with TLS mode.
*
* If you want to run TLS mode without Client certs, just override
* this class and override this method to return false.
*
* @returns true (unless this method is overridden).
*/
protected boolean isRequireClientAuth() {
return true;
}
}
For the convenience of UNIX users, you can download my startup
script from runagent.ksh
or
script from runagent.bash.
You will have to fix the file paths for your environment.
You can also modify it to run other programs as daemons.
Invoke it like nohup ./runagent.ksh
or nohup ./runagent.ksh in order to disassociate
it from your login shell.
$Revision: 2036 $
Copyright © 2007, 2008, 2009
Axis Data Management Corp.