21 Java Naming and Directory Interface (JNDI)/LDAP Lookup Service

The JMX API defines three bindings to lookup services, using existing lookup technologies This examples provide a sample implementation of the JNDI/LDAP Lookup Service. The source code contained in this section is used to create corresponding files in the examples/ directory specified in the appropriate setup procedure and includes:

  • README file

  • Server

  • Client

  • jmx-schema.txt

  • 60jmx-schema.ldif

examples/Lookup/ldap/README


# ==============================================================================
#
# Example of using JNDI/LDAP as Lookup service - registering and looking up
# an RMI Connector (IIOP/JRMP)
#
# ==============================================================================
#
# Requirements:
#
# Before running this example you will have to:
# ---------------------------------------------
#
# * Get access (or install & start) an LDAP directory server that
#   will implement the lookup service.
# * Make sure the Java Schema (RFC 2713: http://www.ietf.org/rfc/rfc2713.txt)
#   is known by that server
# * Update the directory server with JSR 160 LDAP Schema
#   - 60jmx-schema.ldif file provided
#   This ldif file corresponds to the schema described in jmx-schema.txt
#   and can be copied as is in the config/schema directory of
#   the Sun ONE Directory Server.
# * Make sure you have write access to the server so that you can
#   create contexts in which the server will register its URL.
#
# The names used in this example make the assumption that you
# have created a new suffix, a database, and a root node (e.g. dc=Test)
# for the purpose of the example. You may however use any names / location
# you want - just make sure to provide the correct names & URLs
# when starting the Server and Client examples.
#
# In addition, if you wish to use an external directory for the  RMI JMX
# Connectors (URLs of the form jmx:service:[rmi|iiop]:/host:port/jndi/jndi-url)
# then:
#
#   o If you wish to use rmiregistry in conjunction with the RMI/JRMP
#     JMX Connector you will have to start a rmiregistry (see below).
#
#   o If you wish to use CORBA Naming Service in conjunction with the RMI/IIOP
#     JMX Connector you will have to start an ORB daemon (see below).
#
#   o If you wish to use LDAP in conjunction with the RMI JMX Connectors
#     you will have to install/setup a directory server (you can use the
#     same server than that used for Lookup, or another one)
#
# In order to compile and run the example, make a copy of this README file, and
# then simply cut and paste all the commands as needed into a terminal window.
#
# This README makes the assumption that you are running under Java SE 6 on Unix,
# you are familiar with the JMX technology, with LDAP and JNDI, and with
# the bourne shell or korn shell syntax.
#
# All the commands below are defined using Unix korn shell syntax.
#
# If you are not running Unix and korn shell you are expected to be able to
# adapt these commands to your favorite OS and shell environment.
#

#-------------------------------------------------------------------------------
# The directory server must be started first.
#       You will have
#       to make sure the Java Schema (RFC 2713: 
#       http://www.ietf.org/rfc/rfc2713.txt) is known by that server

#-------------------------------------------------------------------------------
# Start an rmiregistry
#
rmiregistry 9999 &

#-------------------------------------------------------------------------------
# Start an ORB daemon:
#
rm -rf ./orb.db
orbd -ORBInitialPort 7777 &

#-------------------------------------------------------------------------------
# Compile Server.java and Client.java
#
# * Server.java: creates an MBeanServer, creates and starts an
#                RMI connector (JRMP/IIOP)
# * Client.java: lookup a connector in JNDI
#                list all MBeans.

javac -d . Server.java Client.java

#-------------------------------------------------------------------------------
# LDAP parameters:
#

# Supply the appropriate hostname below, and define this variable:
#
ldaphost=gigondas

# Supply the appropriate port number below, and define this variable:
#
ldapport=6666

# Supply the appropriate principal below, and define this variable:
#
principal="cn=Directory Manager"

# Supply the appropriate credentials below, and define this variable:
#
credentials=

# Supply the appropriate root under which the Server will try
# to register its URL...
#
provider="ldap://$ldaphost:$ldapport/dc=Test"

#-------------------------------------------------------------------------------
# JNDI URLs
#
jndirmi="rmi://localhost:9999"
jndiiiop="iiop://localhost:7777"
jndildap="ldap://$ldaphost:$ldapport"

#-------------------------------------------------------------------------------
# JMX Service URLs
#
jmxiiopurl="service:jmx:iiop:///jndi/${jndiiiop}/server"
jmxrmiurl="service:jmx:rmi:///jndi/${jndirmi}/server"
jmxiiopldapurl="service:jmx:iiop:///jndi/${jndildap}/cn=x,dc=Test"
jmxrmildapurl="service:jmx:rmi:///jndi/${jndildap}/cn=x,dc=Test"
jmxstuburl="service:jmx:rmi://"
jmxiorurl="service:jmx:iiop://"

#-------------------------------------------------------------------------------
# Below we illustrate the different JMX Connector Servers
# which you have the choice to start. 
# There are seven cases labelled (a) to (f):
#
#   * RMI Connectors
#       + over JRMP
#           - without any external directory (a)
#           - using rmiregistry as external directory (b)
#           - using LDAP as external directory (c)
#       + over IIOP
#           - without any external directory (d)
#           - using CORBA Naming Service as external directory (e)
#           - using LDAP as external directory (f)

# NOTE-1: As defined in section 6.1 "Terminology" of the "JMX Remote API 1.0
# Specification" document, an agent is composed of one MBean Server and of
# one or more Connector Servers. There can be several agents running in one JVM.
# For flexibility of this example, the jndi.Server class creates an agent which
# is composed of one MBean Server and of only one Connector Server. The class
# jndi.Server decides which type of Connector Server to create depending on the
# value given to the "url" system property when you start the example.

# NOTE-2: The value of the "agent.name" system property is the value that the
# jndi.Server class will give to the "AgentName" lookup attribute when it
# registers the connector's URL in the lookup service. As defined in Table 6.1
# "Lookup attributes for connectors" of the "JMX Remote API 1.0 Specification"
# document: the "AgentName" lookup attribute is a simple name used to identify
# the *AGENT* to which the connector is attached. It makes it possible to
# search, with a query to the lookup service, for all the connectors registered
# by a given agent.

# (a) You can start an agent with an RMI Connector Server over JRMP
#     without using any external directory
#
java -classpath . -Ddebug=true \
     -Dagent.name=test-server-a \
     -Durl="service:jmx:rmi://" \
     -Djava.naming.provider.url="$provider" \
     -Djava.naming.security.principal="$principal" \
     -Djava.naming.security.credentials="$credentials" \
     jndi.Server &

# (b) Or you can start an agent with an RMI Connector Server over JRMP
#     using rmiregistry as external directory
#     (Start rmiregistry first, if not yet started)
#
java -classpath . -Ddebug=true \
     -Dagent.name=test-server-b \
     -Durl="service:jmx:rmi:///jndi/${jndirmi}/server" \
     -Djava.naming.provider.url="$provider" \
     -Djava.naming.security.principal="$principal" \
     -Djava.naming.security.credentials="$credentials" \
     jndi.Server &

# (c) Or you can start an agent with an RMI Connector Server over JRMP
#     using LDAP as external directory
#     (First start an LDAP server and create the dc=Test suffix)
#
java -classpath . -Ddebug=true \
     -Dagent.name=test-server-c \
     -Durl="service:jmx:rmi:///jndi/${jndildap}/cn=x,dc=Test" \
     -Djava.naming.provider.url="$provider" \
     -Djava.naming.security.principal="$principal" \
     -Djava.naming.security.credentials="$credentials" \
     jndi.Server &

# (d) Or you can start an agent with an RMI Connector Server over IIOP
#     without using any external directory
#
java -classpath . -Ddebug=true \
     -Dagent.name=test-server-d \
     -Durl="service:jmx:iiop://" \
     -Djava.naming.provider.url="$provider" \
     -Djava.naming.security.principal="$principal" \
     -Djava.naming.security.credentials="$credentials" \
     jndi.Server &

# (e) Or you can start an agent with an RMI Connector Server over IIOP
#     using CORBA Naming Service as external directory
#     (Start ORBD first if not yet started).
#
java -classpath . -Ddebug=true \
     -Dagent.name=test-server-e \
     -Durl="service:jmx:iiop:///jndi/${jndiiiop}/server" \
     -Djava.naming.provider.url="$provider" \
     -Djava.naming.security.principal="$principal" \
     -Djava.naming.security.credentials="$credentials" \
     jndi.Server &

# (f) Or you can start an agent with an RMI Connector Server over IIOP
#     using LDAP as external directory
#     (First start an LDAP server and create the dc=Test suffix)
#
java -classpath . -Ddebug=true \
     -Dagent.name=test-server-f \
     -Durl="service:jmx:iiop:///jndi/${jndildap}/cn=x,dc=Test" \
     -Djava.naming.provider.url="$provider" \
     -Djava.naming.security.principal="$principal" \
     -Djava.naming.security.credentials="$credentials" \
     jndi.Server &

# Once you have started one or more agents, you can start the Client.
#
java -classpath . -Ddebug=true \
     -Djava.naming.provider.url="$provider" \
     -Djava.naming.security.principal="$principal" \
     -Djava.naming.security.credentials="$credentials" \
     jndi.Client

#-------------------------------------------------------------------------------

examples/Lookup/ldap/Server.java


package jndi;

import javax.naming.InitialContext;
import javax.naming.Context;
import javax.naming.NamingEnumeration;
import javax.naming.NameNotFoundException;
import javax.naming.NamingException;

import javax.naming.directory.DirContext;
import javax.naming.directory.Attribute;
import javax.naming.directory.BasicAttribute;
import javax.naming.directory.Attributes;
import javax.naming.directory.BasicAttributes;
import javax.naming.directory.DirContext;
import javax.naming.directory.Attribute;
import javax.naming.directory.Attributes;
import javax.naming.directory.SearchResult;
import javax.naming.directory.SearchControls;

import javax.naming.ldap.InitialLdapContext;

import javax.management.*;
import javax.management.remote.*;
import javax.management.remote.rmi.*;

import java.text.SimpleDateFormat;

import java.util.Date;
import java.util.Map;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.List;
import java.util.ArrayList;
import java.util.Locale;
import java.util.Vector;
import java.io.IOException;
import java.io.Serializable;
import java.net.InetAddress;
import java.net.MalformedURLException;

/**
 * This class demonstrates how to use an LDAP directory as a lookup
 * service for JSR 160 connectors. It shows how to register a
 * JMXConnectorServer with the LDAP directory through JNDI.
 * <p>
 * See README file and {@link #main(String[])} for more details.
 * <p>
 * Make sure to read the section "Binding with Lookup Services" of
 * the JMX Remote API 1.0 Specification before looking at this example.
 */
public class Server {

    // The URL will remain registered for 60 secs.
    //
    public final static int JMX_DEFAULT_LEASE = 60;

    private static boolean debug = false;

    /**
     * The local MBeanServer.
     */
    private final MBeanServer mbs;

    /**
     * Constructs a Server object. Creates a new MBeanServer.
     */
    public Server() {
        mbs = MBeanServerFactory.createMBeanServer();
    }

    /**
     * Get a pointer to the root context of the directory tree
     * under which this server is supposed to register itself.
     * All LDAP DNs will be considered to be relative to that root.
     * <p>
     * Note that this root is not part of the JSR 160 specification,
     * since the actual location where a JMX Agent will register
     * its connectors is left completely open by the specification.
     * The specification only discuss what the JMX Agent must/may
     * put in the directory - but not where.
     * <p>
     * This method assumes that the root of the directory is
     * will be passed in a the {@link Context#PROVIDER_URL
     * Context.PROVIDER_URL} System property.
     * <p>
     * This method will transfer a fixed set of System Properties to
     * the Hashtable given to the JNDI InitialContext:
     * <ul><li>{@link Context#INITIAL_CONTEXT_FACTORY
     *           Context.INITIAL_CONTEXT_FACTORY} - default is
     *         <code>"com.sun.jndi.ldap.LdapCtxFactory"</code></li>
     *     <li>{@link Context#PROVIDER_URL
     *           Context.PROVIDER_URL}</li>
     *     <li>{@link Context#SECURITY_PRINCIPAL
     *           Context.SECURITY_PRINCIPAL} - default is
     *         <code>"cn=Directory Manager"</code></li>
     *     <li>{@link Context#SECURITY_CREDENTIALS
     *           Context.SECURITY_CREDENTIALS}</li>
     * </ul>
     *
     * @return a pointer to the LDAP Directory.
     */
    public static DirContext getRootContext() throws NamingException {
        // Prepare environment
        //
        final Hashtable env = new Hashtable();

        // The Initial Context Factory must be provided, and
        // must point to an LDAP Context Factory
        //
        final String factory =
            System.getProperty(Context.INITIAL_CONTEXT_FACTORY,
                               "com.sun.jndi.ldap.LdapCtxFactory");

        // The LDAP Provider URL must be provided, and
        // must point to a running LDAP directory server
        //
        final String ldapServerUrl =
            System.getProperty(Context.PROVIDER_URL);

        // The LDAP user must be provided, and
        // must have write access to the subpart of the directory
        // where the agent will be registered.
        //
        final String ldapUser =
            System.getProperty(Context.SECURITY_PRINCIPAL,
                               "cn=Directory Manager");

        // Credentials must be provided, so that the user may
        // write to the directory.
        //
        final String ldapPasswd =
            System.getProperty(Context.SECURITY_CREDENTIALS);

        // Debug info: print provided values:
        //
        debug(Context.PROVIDER_URL + "=" + ldapServerUrl);
        debug(Context.SECURITY_PRINCIPAL + "=" + ldapUser);
        if (debug) {
            System.out.print(Context.SECURITY_CREDENTIALS + "=");
            final int len = (ldapPasswd==null)?0:ldapPasswd.length();
            for (int i=0;i<len;i++) System.out.print("*");
            System.out.println();
        }

        // Put provided value in the environment table.
        //
        env.put(Context.INITIAL_CONTEXT_FACTORY,factory);
        env.put(Context.SECURITY_PRINCIPAL, ldapUser);
        if (ldapServerUrl != null)
            env.put(Context.PROVIDER_URL, ldapServerUrl);
        if (ldapPasswd != null)
            env.put(Context.SECURITY_CREDENTIALS, ldapPasswd);

        // Create initial context
        //
        InitialContext root = new InitialLdapContext(env,null);

        // Now return the root directory context.
        //
        return (DirContext)(root.lookup(""));
    }

    /**
     * Registers a JMX Connector URL with the LDAP directory.
     * <p>
     * This method expects to find the LDAP DN where it will register
     * the JMX Connector URL in the "dn" System property. If that
     * property is not set, then "cn=<var>name</var>" is assumed.
     * <p>
     * If the given DN does not point to an existing node in the
     * directory, then this method will attempt to create it. Yet,
     * the parent node must already exist in that case.
     * <p>
     * If the DN points to a node that is already of the <var>jmxConnector</var>
     * class, then this method will simply override its <var>jmxServiceURL</var>
     * ,<var>jmxAgentName</var>, <var>jmxProtocolType</var>,
     * <var>jmxAgentHost</var> and <var>jmxExpirationDate</var> attributes.
     *
     * @param root      A pointer to the root context we are using,
     *                  as returned by {@link #getRootContext()}.
     * @param jmxUrl    A JMX Connector Server URL, that should have
     *                  been obtained from
     *                  {@link JMXConnectorServer#getAddress()
     *                  JMXConnectorServer.getAddress()};
     * @param name      The AgentName with which the URL must be registered
     *                  in the LDAP directory.
     */
    public static void register(DirContext root,
                                JMXServiceURL jmxUrl,
                                String name)
        throws NamingException, IOException {

        // Get the LDAP DN where to register
        //
        final String mydn = System.getProperty("dn","cn="+name);

        debug("dn: " + mydn );

        // First check whether <mydn> already exists
        //
        Object o = null;
        try {
            o = root.lookup(mydn);
            // There is already a node at <mydn>
            //
        } catch (NameNotFoundException n) {
            // <mydn> does not exist! attempt to create it.
            //

            // Prepare attributes for creating a javaContainer
            // with the auxiliary class jmxConnector.
            //
            Attributes attrs = new BasicAttributes();

            // Prepare objectClass attribute: we're going to create
            // a javaContainer with the jmxConnector auxiliary class.
            //
            Attribute objclass = new BasicAttribute("objectClass");
            objclass.add("top");
            objclass.add("javaContainer");
            objclass.add("jmxConnector");
            attrs.put(objclass);
            attrs.put("jmxAgentName", name);
            o = root.createSubcontext(mydn,attrs);
        }

        // That's not supposed to happen but who knows...
        //
        if (o == null) throw new NameNotFoundException();

        // Check that the entry contains the jmxConnector objectClass
        // before modifying the attributes.
        //
        final Attributes attrs = root.getAttributes(mydn);
        final Attribute oc = attrs.get("objectClass");
        if (!oc.contains("jmxConnector")) {
            // The node does not have the jmxConnector class.
            //
            final String msg = "The supplied node [" + mydn + "] does not " +
                "contain the jmxConnector objectclass";
            throw new NamingException(msg);
        }

        // Now need to replace jmxConnector attributes.
        //
        final Attributes newattrs = new BasicAttributes();
        newattrs.put("jmxAgentName",name);
        newattrs.put("jmxServiceURL",jmxUrl.toString());
        newattrs.put("jmxAgentHost",InetAddress.getLocalHost().getHostName());
        newattrs.put("jmxProtocolType",jmxUrl.getProtocol());
        newattrs.put("jmxExpirationDate",
                     getExpirationDate(JMX_DEFAULT_LEASE));
        root.modifyAttributes(mydn,DirContext.REPLACE_ATTRIBUTE,newattrs);
    }

    /**
     * Creates an RMI Connector Server, starts it, and registers it
     * with the LDAP directory.
     * <p>
     * This method will transfer a fixed set of System Properties to
     * the Map given to the RMIConnectorServer constructor. Some
     * JNDI properties, if defined, are transfered to the Map so
     * that they may be used when LDAP is used as external directory
     * to register the RMI Stub (see {@link javax.management.remote.rmi}
     * Javadoc). Note that even if LDAP is used as external directory
     * the {@link Context#INITIAL_CONTEXT_FACTORY
     *            Context.INITIAL_CONTEXT_FACTORY} and
     * {@link Context#PROVIDER_URL Context.PROVIDER_URL} properties
     * usually don't need to be passed.
     * <p>
     * The following System properties, if defined, are transfered to
     * the Map given to the RMIConnectorServer constructor.
     * <ul><li>{@link Context#INITIAL_CONTEXT_FACTORY
     *           Context.INITIAL_CONTEXT_FACTORY}</li>
     *     <li>{@link Context#PROVIDER_URL
     *           Context.PROVIDER_URL}</li>
     *     <li>{@link Context#SECURITY_PRINCIPAL
     *           Context.SECURITY_PRINCIPAL}</li>
     *     <li>{@link Context#SECURITY_CREDENTIALS
     *           Context.SECURITY_CREDENTIALS}</li>
     *     <li>{@link RMIConnectorServer#JNDI_REBIND_ATTRIBUTE
     *           RMIConnectorServer.JNDI_REBIND_ATTRIBUTE} - default
     *           is <code>true</code>.</li>
     * </ul>
     *
     * @param url A string representation of the JMXServiceURL.
     * @return the created RMIConnectorServer.
     */
    public JMXConnectorServer rmi(String url)
        throws IOException, JMException,
               NamingException, ClassNotFoundException {

        // Make a JMXServiceURL from the url string.
        //
        JMXServiceURL jurl = new JMXServiceURL(url);

        // Prepare the environment Map
        //
        final HashMap env = new HashMap();
        final String rprop = RMIConnectorServer.JNDI_REBIND_ATTRIBUTE;
        final String rebind=System.getProperty(rprop,"true");
        final String factory =
            System.getProperty(Context.INITIAL_CONTEXT_FACTORY);
        final String ldapServerUrl =
            System.getProperty(Context.PROVIDER_URL);
        final String ldapUser =
            System.getProperty(Context.SECURITY_PRINCIPAL);
        final String ldapPasswd =
            System.getProperty(Context.SECURITY_CREDENTIALS);

        // Transfer some system properties to the Map
        //
        if (factory!= null) // this should not be needed
            env.put(Context.INITIAL_CONTEXT_FACTORY,factory);
        if (ldapServerUrl!=null) // this should not be needed
            env.put(Context.PROVIDER_URL, ldapServerUrl);
        if (ldapUser!=null) // this is needed when LDAP is used
            env.put(Context.SECURITY_PRINCIPAL, ldapUser);
        if (ldapPasswd != null) // this is needed when LDAP is used
            env.put(Context.SECURITY_CREDENTIALS, ldapPasswd);
        env.put(rprop,rebind); // default is true.

        // Create an RMIConnectorServer
        //
        System.out.println("Creating RMI Connector: " + jurl);
        JMXConnectorServer rmis =
            JMXConnectorServerFactory.newJMXConnectorServer(jurl, env, mbs);

        // Get the AgentName for registering the Connector in the Lookup Service
        //
        final String agentName = System.getProperty("agent.name",
                                                    "DefaultAgent");

        // Start the connector and register it in the LDAP directory.
        //
        start(rmis,env,agentName);

        return rmis;
    }

    /**
     * Start a JMXConnectorServer and register it with the LDAP directory.
     *
     * @param server the JMXConnectorServer to start and register.
     * @param env   the environment Map.
     * @param agentName the AgentName with which the URL must be registered
     *                  in the LDAP Directory. This is not a LDAP DN, but
     *                  the value of the jmxAgentName attribute.
     */
    public void start(JMXConnectorServer server, Map env, String agentName)
        throws IOException, NamingException {

        // Start the JMXConnectorServer
        //
        server.start();

        // Get a pointer to the LDAP directory.
        //
        final DirContext root = getRootContext();

        // Create a JMX Service URL to register in the LDAP directory
        //
        final JMXServiceURL address = server.getAddress();

        // Register the URL in the LDAP directory
        //
        register(root,address,agentName);
    }

    /**
     * Returns a X.208 string representing the GMT date at now + sec.
     *
     * @param sec Number of seconds from now.
     * @return an X.208 GMT GeneralizedTime (ending with Z).
     */
    public static String getExpirationDate(long sec) {
        final SimpleDateFormat fmt =  new SimpleDateFormat("yyyyMMddHHmmss.S");
        final Date date = new Date();
        final Date gmtDate;
        if (fmt.getCalendar().getTimeZone().inDaylightTime(date))
            gmtDate = new Date(System.currentTimeMillis() -
                               fmt.getCalendar().getTimeZone().getRawOffset() -
                               fmt.getCalendar().getTimeZone().getDSTSavings() +
                               1000*sec);
        else
            gmtDate =
                new Date(System.currentTimeMillis() -
                         fmt.getCalendar().getTimeZone().getRawOffset() +
                         1000*sec);
        return ((fmt.format(gmtDate))+"Z");
    }

    /**
     * Trace a debug message.
     */
    private static void debug(String msg) {
        if (debug) System.out.println(msg);
    }

    /**
     * Program Main
     * <p>
     * Creates a server object, gets the JMX Service URL, and calls
     * the method that will create and register the appropriate
     * JMX Connector Server for that URL.
     * <p>
     * You may wish to use the following properties on the Java command line:
     * <ul>
     * <li><code>-Durl=&lt;jmxServiceURL&gt;</code>: specifies the URL of
     *     the JMX Connector Server you wish to use. See README file for more
     *     details.</li>
     * <li><code>-Dagent.name=&lt;AgentName&gt;</code>: specifies an
     *     AgentName to register with.</li>
     * <li><code>-Djava.naming.factory.initial=&lt;initial-context-factory&gt;
     *     </code>: The initial context factory to use for accessing the
     *     LDAP directory (see {@link Context#INITIAL_CONTEXT_FACTORY
     *     Context.INITIAL_CONTEXT_FACTORY}) - default is
     *     <code>"com.sun.jndi.ldap.LdapCtxFactory"</code>.</li>
     * <li><code>-Djava.naming.provider.url=&lt;provider-url&gt;</code>:
     *     The LDAP Provider URL (see {@link Context#PROVIDER_URL
     *     Context.PROVIDER_URL}).</li>
     * <li><code>-Djava.naming.security.principal=&lt;ldap-principal&gt;
     *     </code>: The security principal (login) to use to connect with
     *     the LDAP directory (see {@link Context#SECURITY_PRINCIPAL
     *     Context.SECURITY_PRINCIPAL} - default is
     *     <code>"cn=Directory Manager"</code>.</li>
     * <li><code>-Djava.naming.security.credentials=&lt;ldap-credentials&gt;
     *     </code>: The security credentials (password) to use to
     *     connect with the LDAP directory (see
     *     {@link Context#SECURITY_CREDENTIALS
     *     Context.SECURITY_CREDENTIALS}).</li>
     * <li><code>-Ddebug="true|false"</code>: switch the Server debug flag
     *     on/off (default is "false")</li>
     * </ul>
     */
    public static void main(String[] args) {
        try {
            // Get the value of the debug flag.
            //
            debug = (Boolean.valueOf(System.getProperty("debug","false"))).
                booleanValue();

            // Create a new Server object.
            //
            final Server s = new Server();

            // Get the JMXConnector URL
            //
            final String url =
                System.getProperty("url", "service:jmx:rmi://");

            // Build a JMXServiceURL
            //
            final JMXServiceURL jurl = new JMXServiceURL(url);

            // Creates a JMX Connector Server
            //
            final JMXConnectorServer server;
            debug("Creating Connector: " + jurl);

            final String p = jurl.getProtocol();
            if (p.equals("rmi"))        // Create an RMI Connector
                s.rmi(url);
            else if (p.equals("iiop"))  // Create an RMI/IIOP Connector
                s.rmi(url);
            else                        // Unsupported protocol
                throw new MalformedURLException("Unsupported protocol: " + p);

            System.out.println("\nService URL successfully registered " +
                               "in the LDAP Lookup Service");

        } catch (Exception x) {
            System.err.println("Unexpected exception caught in main: " + x);
            x.printStackTrace(System.err);
        }
    }
}

examples/Lookup/ldap/Client.java


package jndi;

import javax.naming.InitialContext;
import javax.naming.Context;
import javax.naming.NamingEnumeration;
import javax.naming.NameNotFoundException;
import javax.naming.NamingException;

import javax.naming.directory.DirContext;
import javax.naming.directory.Attribute;
import javax.naming.directory.BasicAttribute;
import javax.naming.directory.Attributes;
import javax.naming.directory.BasicAttributes;
import javax.naming.directory.DirContext;
import javax.naming.directory.Attribute;
import javax.naming.directory.Attributes;
import javax.naming.directory.SearchResult;
import javax.naming.directory.SearchControls;

import javax.naming.ldap.InitialLdapContext;

import javax.management.remote.*;
import javax.management.*;

import java.text.SimpleDateFormat;

import java.util.Date;
import java.util.Map;
import java.util.List;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Set;
import java.util.Iterator;
import java.util.Locale;
import java.util.Vector;

import java.io.IOException;
import java.io.Serializable;

/**
 * This class demonstrates how to use an LDAP directory as a lookup service
 * for JSR 160 connectors. It shows how to lookup a JMXServiceURL
 * from the LDAP directory.
 * <p>
 * See README file and {@link #main(String[])} for more details.
 * <p>
 * Make sure to read the section "Binding with Lookup Services" of
 * the JMX Remote API 1.0 Specification before looking at this example.
 */
public class Client {

    private static boolean debug = false;

    /**
     * List all the attributes of an LDAP node.
     *
     * @param root The root DirContext.
     * @param dn   The DN of the node, relative to the root DirContext.
     */
    public static void listAttributes(DirContext root, String dn)
        throws NamingException {
        final Attributes attrs = root.getAttributes(dn);
        System.out.println("dn: " + dn);
        System.out.println("attributes: " + attrs);
    }

    /**
     * Get a pointer to the root context of the directory tree
     * under which this server is supposed to register itself.
     * All LDAP DNs will be considered to be relative to that root.
     * <p>
     * Note that this root is not part of the JSR 160 specification,
     * since the actual location where a JMX Agent will register
     * its connectors is left completely open by the specification.
     * The specification only discuss what the JMX Agent must/may
     * put in the directory - but not where.
     * <p>
     * This method assumes that the root of the directory is
     * will be passed in a the {@link Context#PROVIDER_URL
     * Context.PROVIDER_URL} System property.
     * <p>
     * This method will transfer a fixed set of System Properties to
     * the Hashtable given to the JNDI InitialContext:
     * <ul><li>{@link Context#INITIAL_CONTEXT_FACTORY
     *           Context.INITIAL_CONTEXT_FACTORY} - default is
     *         <code>"com.sun.jndi.ldap.LdapCtxFactory"</code></li>
     *     <li>{@link Context#PROVIDER_URL
     *           Context.PROVIDER_URL}</li>
     *     <li>{@link Context#SECURITY_PRINCIPAL
     *           Context.SECURITY_PRINCIPAL} - default is
     *         <code>"cn=Directory Manager"</code></li>
     *     <li>{@link Context#SECURITY_CREDENTIALS
     *           Context.SECURITY_CREDENTIALS}</li>
     * </ul>
     *
     * @return a pointer to the LDAP Directory.
     */
    public static DirContext getRootContext() throws NamingException {
        // Prepare environment
        //
        final Hashtable env = new Hashtable();

        // The Initial Context Factory must be provided, and
        // must point to an LDAP Context Factory
        //
        final String factory =
            System.getProperty(Context.INITIAL_CONTEXT_FACTORY,
                               "com.sun.jndi.ldap.LdapCtxFactory");

        // The LDAP Provider URL must be provided, and
        // must point to a running LDAP directory server
        //
        final String ldapServerUrl =
            System.getProperty(Context.PROVIDER_URL);

        // The LDAP user must be provided, and
        // must have write access to the subpart of the directory
        // where the agent will be registered.
        //
        final String ldapUser =
            System.getProperty(Context.SECURITY_PRINCIPAL,
                               "cn=Directory Manager");

        // Credentials must be provided, so that the user may
        // write to the directory.
        //
        final String ldapPasswd =
            System.getProperty(Context.SECURITY_CREDENTIALS);

        // Debug info: print provided values:
        //
        debug(Context.PROVIDER_URL + "=" + ldapServerUrl);
        debug(Context.SECURITY_PRINCIPAL + "=" + ldapUser);
        if (debug) {
            System.out.print(Context.SECURITY_CREDENTIALS + "=");
            final int len = (ldapPasswd==null)?0:ldapPasswd.length();
            for (int i=0;i<len;i++) System.out.print("*");
            System.out.println();
        }

        // Put provided value in the environment table.
        //
        env.put(Context.INITIAL_CONTEXT_FACTORY,factory);
        env.put(Context.SECURITY_PRINCIPAL, ldapUser);
        if (ldapServerUrl != null)
            env.put(Context.PROVIDER_URL, ldapServerUrl);
        if (ldapPasswd != null)
            env.put(Context.SECURITY_CREDENTIALS, ldapPasswd);

        // Create initial context
        //
        InitialContext root = new InitialLdapContext(env,null);

        // Now return the root directory context.
        //
        return (DirContext)(root.lookup(""));
    }

    /**
     * Parses the expirationDate in order to determined whether
     * the associated URL has expired.
     *
     * @param expirationDate an X.208 GeneralizedTime, local or GMT.
     *        Only yyyyMMddHHmmss.S (local time) and  yyyyMMddHHmmss.SZ
     *        (GMT time) formats are recognized.
     * @return true if the expirationDate could be parsed and is past,
     *         false otherwise.
     */
    public static boolean hasExpired(String expirationDate) {
        if (expirationDate == null) return false;
        try {
            final Date localExpDate = getLocalDate(expirationDate);
            final Date now = new Date();
            if (localExpDate.before(now)) return true;
        } catch (java.text.ParseException x) {
            x.printStackTrace(System.out);
        }
        return false;
    }

    /**
     * Returns a date in the local time zone parsed from an X.208
     * formatted date. Only yyyyMMddHHmmss.S (local time) and
     * yyyyMMddHHmmss.SZ (GMT time) formats are recognized.
     *
     * @param expirationDate an X.208 GeneralizedTime, local or GMT.
     * @return the corresponding Date in the local time zone.
     */
    public static Date getLocalDate(String expirationDate)
        throws java.text.ParseException {
        final SimpleDateFormat fmt = new SimpleDateFormat("yyyyMMddHHmmss.S");
        Date localDate = fmt.parse(expirationDate);
        if (expirationDate.endsWith("Z")) {
            final Date date = new Date();
            if (fmt.getCalendar().getTimeZone().inDaylightTime(date))
                localDate =
                    new Date(localDate.getTime() +
                             fmt.getCalendar().getTimeZone().getRawOffset() +
                             fmt.getCalendar().getTimeZone().getDSTSavings());
            else
                localDate =
                    new Date(localDate.getTime() +
                             fmt.getCalendar().getTimeZone().getRawOffset());
        }
        return localDate;
    }

    /**
     * Lookup JMXConnectors in the LDAP directory.
     *
     * @param root A pointer to the LDAP directory,
     *        returned by {@link #getRootContext()}.
     * @param protocolType The protocol type of the JMX Connectors
     *        we want to retrieve. If <var>protocolType</var> is null,
     *        then the jmxProtocolType attribute is ignored. Otherwise,
     *        only those agents that have registered a matching
     *        jmxProtocolType attribute will be returned.
     * @param name the AgentName of the JMXConnectors that should
     *        be returned. If <var>name</var> is null, then
     *        the JMXConnectors for all agents are returned
     *        (null is an equivalent for a wildcard).
     * @return The list of matching JMXConnectors retrieved from
     *         the LDAP directory.
     */
    public static List lookup(DirContext root, String protocolType, String name)
        throws IOException, NamingException {

        final ArrayList list = new ArrayList();

        // If protocolType is not null, include it in the filter.
        //
        String queryProtocol =
            (protocolType==null)?"":"(jmxProtocolType="+protocolType+")";

        // Set the LDAPv3 query string
        //
        // Only those node that have the jmxConnector object class are
        // of interest to us, so we specify (objectClass=jmxConnector)
        // in the filter.
        //
        // We specify the jmxAgentName attribute in the filter so that the
        // query will return only those services for which the AgentName
        // attribute was registered. Since JSR 160 specifies that
        // the AgentName attribute is mandatory, this makes it possible
        // to filter out all the services that do not conform
        // to the spec.
        // If <name> is null, it is replaced by "*", so that all
        // services for which the AgentName attribute was specified match,
        // regardless of the value of that attribute.
        // Otherwise, only those services for which AgentName matches the
        // name or pattern specified by <name> will be returned.
        //
        // We also specify (jmxServiceURL=*) so that only those node
        // for which the jmxServiceURL attribute is present will be
        // returned. Thus, we filter out all those node corresponding
        // to agents that are not currently available.
        //
        String query =
            "(&" + "(objectClass=jmxConnector) " +
            "(jmxServiceURL=*) " +
            queryProtocol +
            "(jmxAgentName=" + ((name!=null)?name:"*") + "))";

        System.out.println("Looking up JMX Agents with filter: " + query );

        SearchControls ctrls = new SearchControls();

        // Want to get all jmxConnector objects, wherever they've been
        // registered.
        //
        ctrls.setSearchScope(SearchControls.SUBTREE_SCOPE);

        // Want to get only the jmxServiceUrl and jmxExpirationDate
        // (comment these lines and all attributes will be returned).
        //
        // ctrls.setReturningAttributes(new String[] {
        //           "jmxServiceURL",
        //           "jmxExpirationDate"
        //           });

        // Search...
        //
        final NamingEnumeration results = root.search("", query, ctrls);

        // Get the URL...
        //
        while (results.hasMore()) {

            // Get node...
            //
            final SearchResult r = (SearchResult) results.nextElement();
            debug("Found node: " + r.getName());

            // Get attributes
            //
            final Attributes attrs = r.getAttributes();

            // Get jmxServiceURL attribute
            //
            final Attribute  attr = attrs.get("jmxServiceURL");
            if (attr == null) continue;

            // Get jmxExpirationDate
            //
            final Attribute  exp = attrs.get("jmxExpirationDate");

            // Check that URL has not expired.
            //
            if ((exp != null) && hasExpired((String)exp.get())) {
                System.out.print(r.getName() + ": ");
                System.out.println("URL expired since: " + exp.get());
                continue;
            }

            // Get the URL string
            //
            final String urlStr = (String)attr.get();
            if (urlStr.length() == 0) continue;

            debug("Found URL: " + urlStr);

            // Create a JMXServiceURL
            //
            final JMXServiceURL url  = new JMXServiceURL(urlStr);

            // Create a JMXConnector
            //
            final JMXConnector conn =
                JMXConnectorFactory.newJMXConnector(url,null);

            // Add the connector to the result list
            //
            list.add(conn);
            if (debug) listAttributes(root,r.getName());
        }

        return list;
    }

    /**
     * List all MBeans and their attributes.
     */
    public static void listMBeans(MBeanServerConnection server)
        throws IOException {
        final Set names = server.queryNames(null,null);
        for (final Iterator i=names.iterator(); i.hasNext(); ) {
            ObjectName name = (ObjectName)i.next();
            System.out.println("Got MBean: "+name);
            try {
                MBeanInfo info =
                    server.getMBeanInfo((ObjectName)name);
                MBeanAttributeInfo[] attrs = info.getAttributes();
                if (attrs == null) continue;
                for (int j=0; j<attrs.length; j++) {
                    if (attrs[j].isReadable()) {
                        try {
                            Object o =
                                server.getAttribute(name,attrs[j].getName());
                            System.out.println("\t\t" + attrs[j].getName() +
                                               " = "+o);
                        } catch (Exception x) {
                            System.err.println("JmxClient failed to get " +
                                               attrs[j].getName());
                            x.printStackTrace(System.err);
                        }
                    }
                }
            } catch (Exception x) {
                System.err.println("JmxClient failed to get MBeanInfo: "  + x);
                x.printStackTrace(System.err);
            }
        }
    }

    /**
     * Trace a debug message.
     */
    private static void debug(String msg) {
        if (debug) System.out.println(msg);
    }

    /**
     * Program Main.
     * <p>
     * Lookup all JMX agents in the LDAP Directory and list
     * their MBeans and attributes.
     * <p>
     * You may wish to use the following properties on the Java command line:
     * <ul>
     * <li><code>-Dagent.name=&lt;AgentName&gt;</code>: specifies an
     *     AgentName to lookup (default is null, meaning any agent).</li>
     * <li><code>-Dprotocol=&lt;ProtocolType&gt;</code>: restrains the client
     *     to lookup for a specific protocol type (default is null,
     *     meaning any type).</li>
     * <li><code>-Djava.naming.factory.initial=&lt;initial-context-factory&gt;
     *     </code>: The initial context factory to use for accessing the
     *     LDAP directory (see {@link Context#INITIAL_CONTEXT_FACTORY
     *     Context.INITIAL_CONTEXT_FACTORY}) - default is
     *     <code>"com.sun.jndi.ldap.LdapCtxFactory"</code>.</li>
     * <li><code>-Djava.naming.provider.url=&lt;provider-url&gt;</code>:
     *     The LDAP Provider URL (see {@link Context#PROVIDER_URL
     *     Context.PROVIDER_URL}).</li>
     * <li><code>-Djava.naming.security.principal=&lt;ldap-principal&gt;
     *     </code>: The security principal (login) to use to connect with
     *     the LDAP directory (see {@link Context#SECURITY_PRINCIPAL
     *     Context.SECURITY_PRINCIPAL} - default is
     *     <code>"cn=Directory Manager"</code>.</li>
     * <li><code>-Djava.naming.security.credentials=&lt;ldap-credentials&gt;
     *     </code>: The security credentials (password) to use to
     *     connect with the LDAP directory (see
     *     {@link Context#SECURITY_CREDENTIALS
     *     Context.SECURITY_CREDENTIALS}).</li>
     * <li><code>-Ddebug="true|false"</code>: switch the Server debug flag
     *     on/off (default is "false")</li>
     * </ul>
     */
    public static void main(String[] args) {
        try {
            // Get the value of the debug flag.
            //
            debug = (Boolean.valueOf(System.getProperty("debug","false"))).
                booleanValue();

            // Get a pointer to the LDAP Directory.
            //
            final DirContext root = getRootContext();
            debug("root is: " + root.getNameInNamespace());

            final String protocolType=System.getProperty("protocol");
            final String agentName=System.getProperty("agent.name");

            // Lookup all matching agents in the LDAP Directory.
            //
            List l = lookup(root,protocolType,agentName);

            // Attempt to connect to retrieved agents
            //
            System.out.println("Number of agents found : " + l.size());
            int j = 1;
            for (Iterator i=l.iterator();i.hasNext();j++) {
                JMXConnector c1 = (JMXConnector) i.next();
                if (c1 != null) {

                    // Connect
                    //
                    System.out.println(
                      "----------------------------------------------------");
                    System.out.println("\tConnecting to agent number "+j);
                    System.out.println(
                      "----------------------------------------------------");
                    debug("JMXConnector is: " + c1);

                    // Prepare the environment Map
                    //
                    final HashMap env = new HashMap();
                    final String factory =
                        System.getProperty(Context.INITIAL_CONTEXT_FACTORY);
                    final String ldapServerUrl =
                        System.getProperty(Context.PROVIDER_URL);
                    final String ldapUser =
                        System.getProperty(Context.SECURITY_PRINCIPAL);
                    final String ldapPasswd =
                        System.getProperty(Context.SECURITY_CREDENTIALS);

                    // Transfer some system properties to the Map
                    //
                    if (factory!= null) // this should not be needed
                        env.put(Context.INITIAL_CONTEXT_FACTORY,factory);
                    if (ldapServerUrl!=null) // this should not be needed
                        env.put(Context.PROVIDER_URL, ldapServerUrl);
                    if (ldapUser!=null) // this is needed when LDAP is used
                        env.put(Context.SECURITY_PRINCIPAL, ldapUser);
                    if (ldapPasswd != null) // this is needed when LDAP is used
                        env.put(Context.SECURITY_CREDENTIALS, ldapPasswd);

                    try {
                        c1.connect(env);
                    } catch (IOException x) {
                        System.err.println("Connection failed: " + x);
                        x.printStackTrace(System.err);
                        continue;
                    }

                    // Get MBeanServerConnection
                    //
                    MBeanServerConnection conn =
                        c1.getMBeanServerConnection();
                    debug("Connection is:" + conn);
                    System.out.println("Server domain is: " +
                                       conn.getDefaultDomain());

                    // List all MBeans
                    //
                    try {
                        listMBeans(conn);
                    } catch (IOException x) {
                        System.err.println("Failed to list MBeans: " + x);
                        x.printStackTrace(System.err);
                    }

                    // Close connector
                    //
                    try {
                        c1.close();
                    } catch (IOException x) {
                        System.err.println("Failed to close connection: " + x);
                        x.printStackTrace(System.err);
                    }
                }
            }
        } catch (Exception x) {
            System.err.println("Unexpected exception caught in main: " + x);
            x.printStackTrace(System.err);
        }
    }
}

examples/Lookup/ldap/jmx-schema.txt

--                    LDAP Schema for JSR 160 Lookup
--                    ------------------------------

-- AttributeTypes:
-----------------

-- jmxServiceURL attribute is an IA5 String

( 1.3.6.1.4.1.42.2.27.11.1.1 NAME 'jmxServiceURL' 
  DESC 'String representation of a JMX Service URL' 
  SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 
  SINGLE-VALUE )

-- jmxAgentName attribute is an IA5 String

( 1.3.6.1.4.1.42.2.27.11.1.2 NAME 'jmxAgentName' 
  DESC 'Name of the JMX Agent' 
  SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 
  SINGLE-VALUE )

-- jmxProtocolType attribute is an IA5 String

( 1.3.6.1.4.1.42.2.27.11.1.3 NAME 'jmxProtocolType' 
  DESC 'Protocol used by the registered connector' 
  SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 
  SINGLE-VALUE )

-- jmxAgentHost attribute is an IA5 String

( 1.3.6.1.4.1.42.2.27.11.1.4 NAME 'jmxAgentHost' 
  DESC 'Names or IP Addresses of the host on which the agent is running. 
        When multiple values are given, they should be aliases to the 
        same host.'
  SYNTAX 1.3.6.1.4.1.1466.115.121.1.26  )

-- jmxProperty attribute is an IA5 String

( 1.3.6.1.4.1.42.2.27.11.1.5 NAME 'jmxProperty' 
  DESC 'Java-like property characterizing the registered object. 
        The form of each value should be: "<property-name>=<value>". 
        For instance: "com.sun.jmx.remote.tcp.timeout=200"' 
  SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )

-- jmxExpirationDate attribute is a Generalized Time
-- see [RFC 2252] - or X.208 for a description of
-- Generalized Time

( 1.3.6.1.4.1.42.2.27.11.1.6 NAME 'jmxExpirationDate' 
  DESC 'Date at which the JMX Service URL will be considered obsolete 
        and may be removed from the directory tree' 
  SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 
  SINGLE-VALUE )


-- ObjectClasses:
-----------------

-- jmxConnector class - represents a JMX Connector.
-- must contain the JMX Service URL
-- and the JMX Agent Name

( 1.3.6.1.4.1.42.2.27.11.2.1 NAME 'jmxConnector' 
  DESC  'A class representing a JMX Connector, and containing a 
         JMX Service URL. The jmxServiceURL is not present if the server 
         is not accepting connections' 
  AUXILIARY 
  MUST  ( jmxAgentName  ) 
  MAY   ( jmxServiceURL $ jmxAgentHost $ jmxProtocolType $ jmxProperty $ 
          jmxExpirationDate $ description ) )

examples/Lookup/ldap/60jmx-schema.ldif

dn: cn=schema
attributeTypes: ( 1.3.6.1.4.1.42.2.27.11.1.1 NAME 'jmxServiceURL' 
     DESC 'String representation of a JMX Service URL'
     SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 
     SINGLE-VALUE )
attributeTypes: ( 1.3.6.1.4.1.42.2.27.11.1.2 NAME 'jmxAgentName' 
     DESC 'Name of the JMX Agent' 
     SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 
     SINGLE-VALUE )
attributeTypes: ( 1.3.6.1.4.1.42.2.27.11.1.3 NAME 'jmxProtocolType' 
     DESC 'Protocol used by the registered connector'
     SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 
     SINGLE-VALUE )
attributeTypes: ( 1.3.6.1.4.1.42.2.27.11.1.4 NAME 'jmxAgentHost' 
     DESC 'Names or IP Addresses of the host on which the agent is running. 
  When multiple values are given, they should be aliases to the same host.'
     SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )
attributeTypes: ( 1.3.6.1.4.1.42.2.27.11.1.5 NAME 'jmxProperty' 
     DESC 'Java-like property characterizing the registered object. 
  The form of each value should be: "<property-name>=<value>". 
  For instance: "com.sun.jmx.remote.tcp.timeout=200"'
     SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )
attributeTypes: ( 1.3.6.1.4.1.42.2.27.11.1.6 NAME 'jmxExpirationDate' 
     DESC 'Date at which the JMX Service URL will be considered 
  obsolete and may be removed from the directory tree'
     SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 
     SINGLE-VALUE )
objectClasses: ( 1.3.6.1.4.1.42.2.27.11.2.1 NAME 'jmxConnector'
     DESC  'A class representing a JMX Connector, and containing a 
  JMX Service URL. The jmxServiceURL is not present if the server is 
  not accepting connections'
     AUXILIARY
     MUST  ( jmxAgentName  ) 
     MAY   ( jmxServiceURL $ jmxAgentHost $ jmxProtocolType $ 
             jmxProperty $ jmxExpirationDate $ description ) )