23
votes

I'm using a JNDI for creating connection pool. It works great in a web application. I believe the InitialContext is provided by the tomcat server.

Context initContext  = new InitialContext();
Context envContext  = (Context)initContext.lookup("java:/comp/env");
dataSource = (DataSource)envContext.lookup("jdbc/testdb");

But when I try to call the same utility from a standalone Java program, the initContext object is null. How can I explicitly provide all the necessary properties that Context object is expecting.

Error : javax.naming.NoInitialContextException: Need to specify class name in environment or system property, or as an applet parameter, or in an application resource file: java.naming.factory.initial

6

6 Answers

9
votes

You could also create your own custom context.

LocalContext ctx = LocalContextFactory.createLocalContext();
ctx.addDataSource("jdbc/testdb", driverName, url, usr, pwd);

See Running Beans Locally that use Application Server Data Sources for more details.


newUPDATE

You can use the class org.springframework.mock.jndi.SimpleNamingContextBuilder of Spring. e.g.:

  • Setup:

    SimpleNamingContextBuilder builder = new SimpleNamingContextBuilder();
    builder.bind("jdbc/Oracle", ods);
    builder.activate();
    
  • Use:

    DataSource ds = InitialContext.doLookup("jdbc/Oracle");
    
27
votes

Here is an example adapted from the accepted answer but doing everything inline to avoid creating extra classes.

public static void main(String[] args) {
    setupInitialContext();
    //do something that looks up a datasource
}

private static void setupInitialContext() {
    try {
        NamingManager.setInitialContextFactoryBuilder(new InitialContextFactoryBuilder() {

            @Override
            public InitialContextFactory createInitialContextFactory(Hashtable<?, ?> environment) throws NamingException {
                return new InitialContextFactory() {

                    @Override
                    public Context getInitialContext(Hashtable<?, ?> environment) throws NamingException {
                        return new InitialContext(){

                            private Hashtable<String, DataSource> dataSources = new Hashtable<>();

                            @Override
                            public Object lookup(String name) throws NamingException {

                                if (dataSources.isEmpty()) { //init datasources
                                    MysqlConnectionPoolDataSource ds = new MysqlConnectionPoolDataSource();
                                    ds.setURL("jdbc:mysql://localhost:3306/mydb");
                                    ds.setUser("mydbuser");
                                    ds.setPassword("mydbpass");
                                    dataSources.put("jdbc/mydbname", ds);

                                    //add more datasources to the list as necessary
                                }

                                if (dataSources.containsKey(name)) {
                                    return dataSources.get(name);
                                }

                                throw new NamingException("Unable to find datasource: "+name);
                            }
                        };
                    }

                };
            }

        });
    }
    catch (NamingException ne) {
        ne.printStackTrace();
    }
}
1
votes

There isn't a way to directly use the Tomcat Context Factory, see here for a little more documentation on the alternatives. But I recommend you try running a registry outside of Tomcat...

// select the registry context factory for creating a context
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.rmi.registry.RegistryContextFactory");

// specify where that factory is running.
env.put(Context.PROVIDER_URL, "rmi://server:1099");

// create an initial context that accesses the registry
Context ctx = new InitialContext(env);

You could change your code in Tomcat to also use this external RegistryContext and then both set(s) would be using the same JNDI provider. This question seems very similar.

1
votes

You can create your own Context by sub-classing javax.naming.InitialContext and implementing only a small subset of methods, typically the bind and the lookup methods.

Then you can create your data source and bind it to your initial context to a specific key. After this you are ready to go and query from any place your JNDI context in your stand-alone Java programme.

This is the code you can use to create your own context:

InitialContext initialContext = new InitialContext() {

    private Map<String, Object> table = new HashMap<>();

    public void bind(String key, Object value) {
        table.put(key, value);
    }

    public Object lookup(String key) throws NamingException {
        return table.get(key);
    }
};

// Activate the initial context
NamingManager.setInitialContextFactoryBuilder(environment -> environment1 -> initialContext);

Then you can initialise your data source, whichever you choose:

InitialContext ic = new InitialContext();

BasicDataSource bds = new BasicDataSource();
bds.setDriverClassName("com.microsoft.sqlserver.jdbc.SQLServerDriver");
bds.setUrl(url);
bds.setUsername(username);
bds.setPassword(password);

ic.bind(jndiPath, bds);

And somewhere else in your code, you can use the existing data source by retrieving it from the JNDI context:

InitialContext ic2 = new InitialContext();
DataSource ds = (DataSource) ic2.lookup(jndiPath);
assertNotNull(ds);
Connection conn = ds.getConnection();
assertNotNull(conn);
conn.close();
0
votes

Tomcat provides Context & DataSource implementations that work with the InitialContext class. When running Tomcat the Context.INITIAL_CONTEXT_FACTORY property is set to point to Tomcat's implementations. When not running Tomcat, you don't have this ability... you need to use a third-party-library like c3p0 for connection pooling.

0
votes

You can create an initial context using blow code.

InitialContext ic = new InitialContext();
    // Retrieve the Home interface using JNDI lookup
    Object helloObject = ic.lookup("java:comp/env/ejb/HelloBean");

if you want create custom initial context, you can extends javax.naming.InitailContext class