Exposing Data Sources as JNDI Resources
Java Naming and Directory Interface (JNDI) is a Java application programming interface (API) providing naming and directory functionality for Java software clients to discover and look up data and objects via a name. WSO2 Application Server supports JNDI InitialContext implementation by inheriting the JNDI implementation of Tomcat (http://tomcat.apache.org/tomcat-7.0-doc/jndi-resources-howto.html). JNDI helps decouple object creation from the object look-up. When you have registered a data source with JNDI, others can discover it through a JNDI look-up and use it.
The following sections describe how to register and look up JNDI resources, and how to expose data sources as JNDI resources.
Registering JNDI Resources
First, register JNDI resources for webapps context. Registering can be done in two ways - webapp-level or global level.
Webapp-Level Registering
To register a JNDI resource on a webapp-level, place the JNDI resource in the context.xml file of the webapp. Since each webapp’s context is isolated from each other, this ensures that the resources will be available to the webapp context only.
For example,
<Resource name="jdbc/TestDataSource" auth="Container" type="javax.sql.DataSource" driverClassName="com.mysql.jdbc.Driver" maxActive="100" maxIdle="30" maxWait="10000" url="jdbc:mysql://localhost:3306/test_db" username="root" password="root"/>
The example above defines a database resource and its properties. You can define any number of resources. Properties of a resource should go inside its <Resource> element.
Global Registering
Place resources under the <GlobalNamingResources> element in tomcat's server server configuration file (<PRODUCT_HOME>repository/conf/tomcat/catalina-server.xml in WSO2 products). Then, the resources can be referenced by linking them in the webapp's context.xml file. Global resources are visible to all webapps deployed in the server.
For example,
<ResourceLink name="jdbc/TestDataSource" global="jdbc/TestDataSource" type="javax.sql.DataSource"/>
Note
Finally, have a reference to those resources in the web.xml of the webapp. For example,
<resource-ref> <description>Your Description</description> <res-ref-name>jdbc/TestDataSource</res-ref-name> <res-type>javax.sql.DataSource</res-type> <res-auth>Container</res-auth> </resource-ref>
JNDI Look-up
Now that you are done registering JNDI resources, let's see how to look them up. In the example code segment below, SelectorContext is the Tomcat JNDI Context implementation. Similarly, build the Catalina selector context using the IntialContext environment, using which you can look up the JNDI resources.
Hashtable environment = new Hashtable(); environment.put("java.naming.factory.initial", "org.wso2.carbon.tomcat.jndi.CarbonJavaURLContextFactory"); environment.put("java.naming.webapp.bound", true); Context initCtx = new InitialContext(environment); Context initialContext = (Context) initCtx.lookup("java:comp/env");
Once done, you can look up for the resource defined earlier. For example,
DataSource ds = (DataSource) initialContext.lookup("jdbc/TestDataSource");
Accessing JNDI Resources within the Webapps
You can expose Data Sources as JNDI resources form the management console UI of the Application Server as explained in section Expose as a JNDI Data Source.
Next, let's take a look at how to access these JNDI resources within the webapps that are hosted in the same Application Server instance. This was not possible in previous versions due to the way JNDI context isolation is implemented for webapps at tomcat level. The implementation states that one webapp’s JNDI context cannot be accessed by another webapp, unless the JNDI resources are registered globally. Since webapps run isolated from each other, their contexts will also be isolated. The JNDI context of carbon is also usually isolated from webapps.
Shown below is how the naming context factory class (i.e. org.apache.naming.java.javaURLContextFactory), which is used as the default Initial Context Factory class in WSO2 Carbon products, is implemented:
public Context getInitialContext(Hashtable<?, ?> environment) throws NamingException { if (ContextBindings.isThreadBound() || (ContextBindings.isClassLoaderBound())) { // Redirect the request to the bound initial context return new SelectorContext( (Hashtable<String, Object>) environment, true); } // If the thread is not bound, return a shared writable context if (initialContext == null) { synchronized (javaURLContextFactory.class) { if (initialContext == null) { initialContext = new NamingContext( (Hashtable<String, Object>) environment, MAIN); } } } }
In order to access JNDI resources within webapps, WSO2 has written a InitialContextFactory, which extends the above class and overrides the above methods. This new implementation of javaUrlContextFactory in WSO2 Carbon products does not check whether the current context is bound to a thread or class loader. It returns the JNDI of the Carbon runtime.
The following segment is set as the default Initial Context Factory class in <PRODUCT_HOME>/repository/conf/carbon.xml file.
<DefaultInitialContextFactory>org.wso2.carbon.tomcat.jndi.CarbonJavaURLContextFactory</DefaultInitialContextFactory>
When you want to access the Carbon JNDI Context to lookup for Data Sources, add the following JNDI Initial Context Factory Environment property:
java.naming.factory.initial : "org.wso2.carbon.tomcat.jndi.CarbonJavaURLContextFactory"
The following steps explain how this works.
1. Log on to the product's management console and select "Data Sources."
2. Click on the "Add Data Source" link in the "Data Sources" page.
3. Select RDBMS as the Data Source Type. Provide the rest of the details accordingly. You will find an option to expose this data source as a JNDI Data Source at the end of the screen.
In this example, we are exposing the new Data Source as jdbc/MyCarbonDataSource.
- Name : Name of the JNDI data source which will be visible to others in object look-up.
- Use Data Source Factory : If the data source needs to be accessed from an external environment, the "Data Source Factory" option should be used. When it is selected, a reference object is created with defined data source properties. When accessing the data source from an external environment, the "Data Source Factory" creates a data source instance based on the values of the reference object. In the configuration, this is set as useDataSourceFactory="true".
- JNDI Properties : Properties related to the jndi data source (username, password etc). If the "Data Source Factory" is selected, following properties should be specified.
- java.naming.factory.initial - Used to select the registry service provider as the initial context.
- java.naming.provider.url - Specifies the location of the registry when the registry is being used as the initial context.
4. Click Save to complete. It will expose the Data Source.
5. The code segment below shows how to access this exposed Data Source within a webapp.
try { Hashtable environment = new Hashtable(); environment.put("java.naming.factory.initial", "org.wso2.carbon.tomcat.jndi.CarbonJavaURLContextFactory"); Context initContext = new InitialContext(environment); Object result = initContext.lookup("jdbc/MyCarbonDataSource"); if (result != null) { // Do your work here } else { System.out.println(“Cannot find MyCarbonDataSource”); } } catch (NamingException e) { e.printStackTrace(); }
In the above code segment, we create an IntialContext with the relevant environment properties. It gives the JNDI context of Carbon, using which you can look for any object that is bound to that context. Here you are looking jdbc/MyCarbonDataSource up.
Similarly, you can look up any Data Source, which is exposed as a JNDI resource, from webapps.