9

For testing purposes, I'm looking for a simple way to start a standalone JNDI server, and bind my javax.sql.DataSource to "java:/comp/env/jdbc/mydatasource" programmatically.

The server should bind itself to some URL, for example: "java.naming.provider.url=jnp://localhost:1099" (doesn't have to be JNP), so that I can look up my datasource from another process. I don't care about which JNDI server implementation I'll have to use (but I don't want to start a full-blown JavaEE server).

This should be so easy, but to my surprise, I couldn't find any (working) tutorial.

Chris Lercher
  • 37,264
  • 20
  • 99
  • 131
  • 1
    I'm not sure this question entirely makes sense. JNDI is an API, not a protocol. The API provides access to various naming protocols. So are you really asking for a way to start a server for some arbitrary naming protocol that you can use JNDI against? Then you're going to bind a DataSource in it. But that DataSource will exist in the server process, right? So what happens when a client in another process asks for it? The whole `java:` namespace is usually very much an in-process thing. – Tom Anderson May 02 '11 at 21:24
  • @Tom: I didn't want to go into that detail, but I'm actually trying to bind a c3p0 ComboPooledDataSource - and the documentation claims, that "c3p0 DataSources are both Referenceable and Serializable, and are thus suitable for binding to a wide-variety of JNDI-based naming services". – Chris Lercher May 02 '11 at 21:39
  • Okay, that seems reasonable then. I wonder how they do that? What clever chaps they must be. – Tom Anderson May 03 '11 at 11:32
  • There is no such thing as a JNDI server. You have to decide what kind of server you want to talk to LDAP, COSnaming, RMI registry, DNS, ... and use JNDI as a *client-side* technology to talk to it. The question doesn't make sense. – user207421 Sep 13 '14 at 02:53
  • @EJP: The name "JNDI server" is not very precise indeed. However, what I meant back then (over 3 years ago...), was a server that can be accessed by a JNDI client (I didn't care about which one, as long as it allows me to bind the object in a way, that the client can read it. The notion of a "JNDI server" was mostly derived from c3p0's mentioning of "JNDI-based naming services".) – Chris Lercher Sep 15 '14 at 18:00

8 Answers8

10

The JDK contains a JNDI provider for the RMI registry. That means you can use the RMI registry as a JNDI server. So, just start rmiregistry, set java.naming.factory.initial to com.sun.jndi.rmi.registry.RegistryContextFactory, and you're away.

The RMI registry has a flat namespace, so you won't be able to bind to java:/comp/env/jdbc/mydatasource, but you will be able to bind to something so it will accept java:/comp/env/jdbc/mydatasource, but will treat it as a single-component name (thanks, @EJP).

I've written a small application to demonstrate how to do this: https://bitbucket.org/twic/jndiserver/src

I still have no idea how the JNP server is supposed to work.

Tom Anderson
  • 46,189
  • 17
  • 92
  • 133
  • It doesn't 'mean you can use the RMI registry as a JNDI server'. There is no such thing as a JNDI server. It means you can access the RMI Registry via a JNDI *client.* – user207421 Sep 13 '14 at 02:52
  • 2
    @EJP: I know what you mean. But it seems to me that if you can use a P client to access Q, then Q may *usefully be described as* a P server. Moreover, Chris used the term "JNDI server", and defined what that meant to him; if there is something which fits that definition, who are we to trample on another man's idiolect by telling him it isn't one? – Tom Anderson Sep 19 '14 at 20:30
  • I'm a stickler for correct terminology, that's who. If electronics engineers were this sloppy we would all have been electrocuted long ago. NB you *can* use a name like `java:/comp/env/jdbc/mydatasource`, but you can't use (e.g. list) any of its intermediate names. But when I try this I get `Exception in thread "main" java.lang.IllegalArgumentException: RegistryContext: object to bind must be Remote, Reference, or Referenceable`. – user207421 Nov 24 '15 at 23:20
  • @EJP: Yes, you see the thing is that the object to bind must be Remote, Reference, or Referenceable. It sounds like you were attempting to bind one which was not. Fortunately, the ComboPooledDataSource which Chris wanted to bind is such a thing. I've written a tiny application to prove it, by sharing just such a data source using the RMI registry as a JNDI server: https://bitbucket.org/twic/jndiserver/src – Tom Anderson Nov 26 '15 at 23:15
  • @Tom That's my point. You can't bind just anything into an RMI Registry, JNDI or no JNDI: a `DataSource`, for example, which is what the question is about. – user207421 Mar 28 '17 at 05:26
5

I worked on the John´s code and now is working good.

In this version I'm using libs of JBoss5.1.0.GA, see jar list below:

  • jboss-5.1.0.GA\client\jbossall-client.jar
  • jboss-5.1.0.GA\server\minimal\lib\jnpserver.jar
  • jboss-5.1.0.GA\server\minimal\lib\log4j.jar
  • jboss-remote-naming-1.0.1.Final.jar (downloaded from http://search.maven.com)

This is the new code:

import java.net.InetAddress;
import java.util.Hashtable;
import java.util.concurrent.Callable;

import javax.naming.Context;
import javax.naming.InitialContext;

import org.jnp.server.Main;
import org.jnp.server.NamingBeanImpl;

public class StandaloneJNDIServer implements Callable<Object> {

public Object call() throws Exception {

    setup();

    return null;
}

@SuppressWarnings("unchecked")
private void setup() throws Exception {

    //configure the initial factory
    //**in John´s code we did not have this**
    System.setProperty(Context.INITIAL_CONTEXT_FACTORY, "org.jnp.interfaces.NamingContextFactory");

    //start the naming info bean
    final NamingBeanImpl _naming = new NamingBeanImpl();
    _naming.start();

    //start the jnp serve
    final Main _server = new Main();
    _server.setNamingInfo(_naming);
    _server.setPort(5400);
    _server.setBindAddress(InetAddress.getLocalHost().getHostName());
    _server.start();

    //configure the environment for initial context
    final Hashtable _properties = new Hashtable();
    _properties.put(Context.INITIAL_CONTEXT_FACTORY, "org.jnp.interfaces.NamingContextFactory");
    _properties.put(Context.PROVIDER_URL,            "jnp://10.10.10.200:5400");

    //bind a name
    final Context _context = new InitialContext(_properties);
    _context.bind("jdbc", "myJDBC");

}

public static void main(String...args){

    try{

        new StandaloneJNDIServer().call();

    }catch(Exception _e){
        _e.printStackTrace();
    }

}
}

To have good logging, use this log4j properties:

log4j.rootLogger=TRACE, A1 
log4j.appender.A1=org.apache.log4j.ConsoleAppender 
log4j.appender.A1.layout=org.apache.log4j.PatternLayout 
log4j.appender.A1.layout.ConversionPattern=%-4r [%t] %-5p %c %x - %m%n

To consume the Standalone JNDI server, use this client class:

import java.util.Hashtable;

import javax.naming.Context;
import javax.naming.InitialContext;

/**
 * 
 * @author fabiojm - Fábio José de Moraes
 *
 */
public class Lookup {

public Lookup(){

}

@SuppressWarnings("unchecked")
public static void main(String[] args) {

    final Hashtable _properties = new Hashtable();

    _properties.put("java.naming.factory.initial", "org.jnp.interfaces.NamingContextFactory");
    _properties.put("java.naming.provider.url",    "jnp://10.10.10.200:5400");

    try{
        final Context _context = new InitialContext(_properties);

        System.out.println(_context);
        System.out.println(_context.lookup("java:comp"));
        System.out.println(_context.lookup("java:jdbc"));

    }catch(Exception _e){
        _e.printStackTrace();
    }
}

}
Fábio
  • 181
  • 2
  • 4
3

Here's a code snippet adapted from JBoss remoting samples. The code that is in the samples (version 2.5.4.SP2 ) no longer works. While the fix is simple it took me more hours than I want to think about to figure it out. Sigh. Anyway, maybe someone can benefit.

package org.jboss.remoting.samples.detection.jndi.custom;

import java.net.InetAddress;  
import java.util.concurrent.Callable;

import org.jnp.server.Main;
import org.jnp.server.NamingBeanImpl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class StandaloneJNDIServer implements Callable<Object> { 

    private static Logger logger = LoggerFactory.getLogger( StandaloneJNDIServer.class );

   // Default locator values - command line args can override transport and port
   private static String transport = "socket";
   private static String host = "localhost";
   private static int port = 5400;
   private int detectorPort = 5400;

    public StandaloneJNDIServer() {}

    @Override
    public Object call() throws Exception {

        StandaloneJNDIServer.println("Starting JNDI server... to stop this server, kill it manually via Control-C");

        //StandaloneJNDIServer server = new StandaloneJNDIServer();
        try {
            this.setupJNDIServer();

            // wait forever, let the user kill us at any point (at which point, the client will detect we went down)
            while(true) {
                Thread.sleep(1000);
            }
        }
        catch(Exception e) {
            e.printStackTrace();
        }

        StandaloneJNDIServer.println("Stopping JBoss/Remoting server");
        return null;

    }

    private void setupJNDIServer() throws Exception
    {
        // start JNDI server
        String detectorHost = InetAddress.getLocalHost().getHostName();

        Main JNDIServer = new Main();

 // Next two lines add a naming implemention into
 // the server object that handles requests. Without this you get a nice NPE.
        NamingBeanImpl namingInfo = new NamingBeanImpl();
        namingInfo.start();

        JNDIServer.setNamingInfo( namingInfo );
        JNDIServer.setPort( detectorPort );
        JNDIServer.setBindAddress(detectorHost);
        JNDIServer.start();
        System.out.println("Started JNDI server on " + detectorHost + ":" + detectorPort );
    }

    /**
     * Outputs a message to stdout.
     *
     * @param msg the message to output
     */
    public static void println(String msg)
    {
        System.out.println(new java.util.Date() + ": [SERVER]: " + msg);
    } 
}
John Goyer
  • 51
  • 1
2

I know I'm late to the party, but I ended up hacking this together like so

InitialContext ctx = new InitialContext();

// check if we have a JNDI binding for "jdbc". If we do not, we are
// running locally (i.e. through JUnit, etc)
boolean isJndiBound = true;
try {
    ctx.lookup("jdbc");
} catch(NameNotFoundException ex) {
    isJndiBound = false;
}

if(!isJndiBound) {
    // Create the "jdbc" sub-context (i.e. the directory)
    ctx.createSubcontext("jdbc");

    //parse the jetty-web.xml file
    Map<String, DataSource> dataSourceProperties = JettyWebParser.parse();

    //add the data sources to the sub-context
    for(String key : dataSourceProperties.keySet()) {
        DataSource ds = dataSourceProperties.get(key);
        ctx.bind(key, ds);
    }
}
Mike K
  • 29
  • 1
  • 1
    `javax.naming.NoInitialContextException: Need to specify class name in environmen t or system property, or as an applet parameter, or in an application resource file: java.naming.factory.initial`. This doesn't work if there isn't already a provider specified. Which is what the question is about, actually. – user207421 Nov 24 '15 at 23:23
0

For local, one process standalone jar purpouses I would use spring-test package:

    SimpleNamingContextBuilder builder = new SimpleNamingContextBuilder();
    SQLServerConnectionPoolDataSource myDS = new SQLServerConnectionPoolDataSource();
    //setup...
    builder.bind("java:comp/env/jdbc/myDS", myDS);
    builder.activate();

startup log:

22:33:41.607 [main] INFO org.springframework.mock.jndi.SimpleNamingContextBuilder - Static JNDI binding: [java:comp/env/jdbc/myDS] = [SQLServerConnectionPoolDataSource:1]
22:33:41.615 [main] INFO org.springframework.mock.jndi.SimpleNamingContextBuilder - Activating simple JNDI environment
MGorgon
  • 2,547
  • 23
  • 41
0

I have been looking for a similar simple starter solution recently. The "file system service provider from Sun Microsystems" has worked for me well. See https://docs.oracle.com/javase/jndi/tutorial/basics/prepare/initial.html.

The problem with the RMI registry is that you need a viewer - here you just need to look at file contents.

You may need fscontext-4.2.jar - I obtained it from http://www.java2s.com/Code/Jar/f/Downloadfscontext42jar.htm

bczoma
  • 181
  • 2
  • 10
0

Have you considered using Mocks? If I recall correctly you use Interfaces to interact with JNDI. I know I've mocked them out at least once before.

As a fallback, you could probably use Tomcat. It's not a full blown J2EE impl, it starts fast, and is fairly easy to configure JNDI resources for. DataSource setup is well documented. It's sub-optimal, but should work.

rfeak
  • 8,124
  • 29
  • 28
0

You imply you've found non-working tutorials; that may mean you've already seen these:

I had a quick go, but couldn't get this working. A little more perseverance might do it, though.

Tom Anderson
  • 46,189
  • 17
  • 92
  • 133
  • Yes, I stumbled upon the second tutorial, and invested a few hours, but couldn't get it working (there seems to be something broken with "org.jnp.server.Main"). I now quickly looked at the first tutorial - but it seems, this is also based on "org.jnp.server.Main". I think, all of this must work somehow (after all, Jboss probably uses it internally, too?!) Would be great, if somebody gets this working, as this would be exactly what I am looking for. – Chris Lercher May 02 '11 at 21:36
  • @Chris Lercher what problems are you seeing exactly? – Wes May 03 '11 at 08:02
  • @Wes: Starting "org.jnp.server.Main" results in a NullPointerException; it's the one described in http://myjavanotebook.blogspot.com/2008/04/use-jndi-with-your-j2se-application.html . The article proposes a workaround, but that doesn't work for me either: It avoids the NPE, but it simply doesn't seem to bind a JNP server to any socket at all... – Chris Lercher May 03 '11 at 09:34
  • I tried it, and also got the same problem. I read through the code, and the NPE comes from a variable called theServer being null. I couldn't find any way that, in the context of the standalone app, it would ever not be null! I suspect that the standalone server is broken, and has been so for years. JBoss never uses it standalone, so why would they care? – Tom Anderson May 03 '11 at 11:33
  • This here may be interesting: http://docs.jboss.org/hornetq/2.2.2.Final/user-manual/en/html/using-jms.html#d0e1229 So it seems to be possible to run "org.jnp.server.Main" together with HornetQ (a standalone JMS server from JBoss). They do basically the same setup as we did, and inject a "Naming" bean (which is just an instance of "org.jnp.server.NamingBeanImpl"). I tried doing just that in plain Java code - but it fails with an NPE in "sun.rmi.server.UnicastServerRef". Something must be different when it's run in HornetQ, but I can't figure it out. – Chris Lercher May 03 '11 at 12:45