Issue
I am using Tomcat9 to deploy multiple applications which share the same datasource connection pool(s). I have achieved the purpose by:
- Adding the datasource as Resource elements under GlobalNamingResources in conf/server.xml,
- Use ResourceLink element in conf/context.xml to link global JNDI resource to make it available to ALL web applications, and
- Access the Datasource using JNDI from web applications.
It is working as expected.
However I got a requirement to avoid editing any xml (server.xml or conf/context.xml or app specific context.xml) and to try to achieve Step 1 and 2 programmatically. If so, the advantage is, only a jar which needs to be added in /lib needs to be modified to support a new datasource with absolutely no modifications on any xml (or may be with minimum configuration in server.xml).
Is there any way to achieve this requirements of defining a Resource as GlobalNamingResources and link it to web application via ResourceLink, all via Java?
Solution
I have achieved my requirements after going thru the tomcat javadocs. Given below the details:
1. Adding a Datasource as GlobalNamingResource
For this, create a server lifecycle listener, create a datasource and add it to the JNDI GlobalNamingContext.
public class GlobalDatasourceCreator implements LifecycleListener {
@Override
public void lifecycleEvent(LifecycleEvent event) {
if (event.getSource() instanceof Server) {
Context namingContext = ((Server) event.getSource()).getGlobalNamingContext();
if (Lifecycle.START_EVENT.equals(event.getType())) {
bindDatasources(namingContext);
} else if (Lifecycle.STOP_EVENT.equals(event.getType())) {
unbindDatasources(namingContext);
}
}
}
private void bindDatasources(Context namingContext) {
if (createSubContext(namingContext)) {
try {
DataSource ds = getDatasource(); //TODO: Implement it
namingContext.rebind("jdbc/myds_global", ds);
} catch (Exception e) {
e.printStackTrace();
}
}
}
private boolean createSubContext(Context namingContext) {
try {
namingContext.createSubcontext("jdbc");
} catch (NameAlreadyBoundException e) {
} catch (NamingException e) {
return false;
}
return true;
}
private void unbindDatasources(Context namingContext) {
try {
namingContext.unbind("jdbc/myds_global");
} catch (NamingException e) {
e.printStackTrace();
}
}
Then add this class to the conf/server.xml as a listener
<Listener className="com.test.GlobalDatasourceCreator" />
2. Exposing the Datasource to all web applications via ResourceLink
Create a context LifecycleListener. On the START event, create a ResourceLink and attach it to the context.
Note: Since this is a listener at Context level, the ResourceLink will be created for all the applications. In my requirement was to expose it to all applications since it is a controlled environment. Filtering based on context name can be applied if ResourceLink needs to be created only for selected applications.
public class AppDatasourceLinkCreator implements LifecycleListener {
@Override
public void lifecycleEvent(LifecycleEvent event) {
if (event.getSource() instanceof Context) {
Context ctx = (Context) event.getSource();
if (Lifecycle.START_EVENT.equals(event.getType())) {
addResourceLink(ctx);
} else if (Lifecycle.STOP_EVENT.equals(event.getType())) {
removeResourceLink(ctx);
}
}
}
private void removeResourceLink(Context ctx) {
ctx.getNamingResources().removeResourceLink("jdbc/myds");
}
private void addResourceLink(Context ctx) {
ContextResourceLink resourceLink = new ContextResourceLink();
resourceLink.setGlobal("jdbc/myds_global");
resourceLink.setName("jdbc/myds");
resourceLink.setType("javax.sql.DataSource");
ctx.getNamingResources().addResourceLink(resourceLink);
}
}
Then add this class to the conf/context.xml as a listener
<Listener className="com.test.AppDatasourceLinkCreator" />
Create a jar containing these two classes and place it in the /lib folder.
Advantage: No further xml modifications are required to add any number of datasources. Just modify the java code to add new datasource, update the jar in lib folder and restart the server, which perfectly match my project requirements. This also takes care of the problem of exposing the datasource credentials in plain text in xml (although not 100% risk proof).
Answered By - Devadas