26

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

BalusC
  • 1,082,665
  • 372
  • 3,610
  • 3,555
Satish Jonnala
  • 619
  • 3
  • 9
  • 21

7 Answers7

28

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();
    }
}
Scott Nelson
  • 839
  • 9
  • 8
10

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.


new UPDATE

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");
    
Paul Vargas
  • 41,222
  • 15
  • 102
  • 148
  • 1
    Please note, as the last comments of the article you linked indicated that the solutions is buggy: A StackOverflowException is thrown during the lookup if the name is null or not found... – Lonzak Jul 08 '16 at 15:23
  • Really nice. Set save my test. I manipulate the context which was used in an implementation of an interface. – ziodraw Mar 13 '18 at 14:38
  • I am not using spring or any java EE technology. i still want to use local context at server startup. – Prachi Mar 30 '18 at 22:24
4

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();
gil.fernandes
  • 12,978
  • 5
  • 63
  • 76
1

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.

Community
  • 1
  • 1
Elliott Frisch
  • 198,278
  • 20
  • 158
  • 249
1

You can configure a standalone app (or unit tests!) to use your server's INITIAL_CONTEXT_FACTORY without running the server.

This is how to do that for a Apache Tomcat initialization, but you should have enough information to adapt it for another server.

Starting configuration from the server

This example presumes you want to mimic something like this from your context.xml file:

<Resource name="jdbc/testdb" auth="Container"
  type="javax.sql.DataSource"
  driverClassName="com.microsoft.sqlserver.jdbc.SQLServerDriver"
  username="someuser" password="somepassword"
  url="jdbc:sqlserver://localhost\SQLEXPRESS;databaseName=somedb;applicationName=someapp"/>

Create the resource to go into the Context

This example is database specific, but you can put things other than a DataSource in the Context. Here is one way you might be able to create a DataSource for this SQL Server example. (It requires the JDBC driver and tomcat-dbcp.jar in your classpath. This code is for an older version of the driver, new versions probably have SQLServerXADataSource and would not need tomcat-dbcp.jar.)

BasicDataSource dataSource = new BasicDataSource();
dataSource.setDriverClassName(SQLServerDriver.class.getName());
dataSource.setUrl(url);
dataSource.setUsername(getUserName());
dataSource.setPassword(getPassword());

Initialize using Tomcat INITIAL_CONTEXT_FACTORY

See this old blog post.

System.setProperty(Context.INITIAL_CONTEXT_FACTORY,
        "org.apache.naming.java.javaURLContextFactory");
System.setProperty(Context.URL_PKG_PREFIXES, "org.apache.naming");
InitialContext ctxt = new InitialContext();`

The reference to org.apache.naming.java.javaURLContextFactory means you should have catalina.jar in your classpath. You should be able to find variants for the INITIAL_CONTEXT_FACTORY and URL_PGK_PREFIXES values for other application servers.

Register the resource for future lookups

Then register your DataSource dataSource for future lookups, at java:/comp/env/jdbc/testdb. Unfortunately, you have to make sure all the intermediate subcontext levels are created before you can add the resource. (Exercise for the reader: You can create a method to figure out the subcontext names from the resourceName instead of hard-coding the list as this example does.)

// Create all the intermediate levels
for (String resource : new String[]{ "java:", "java:comp", "java:comp/env", "java:/comp/env/jdbc" })
{
    try
    {
        ctxt.lookup(subContext);
    }
    catch (NameNotFoundException nnfe)
    {
        ctxt.createSubcontext(subContext);
    }
}

// Finally, register the resource
ctxt.bind(resourceName, dataSource);

This completes the setup portion, mimicking what is normally done in the Tomcat application server context.xml.

Do the lookup

Then the rest of your application can create InitialContexts and do lookups as normal (in a single lookup call):

Context ic = new InitialContext();
DataSource ds = (DataSource)ic.lookup("java:/comp/env/jdbc/testdb");
Pixelstix
  • 702
  • 6
  • 21
0

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.

ClickerMonkey
  • 1,881
  • 16
  • 13
0

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