How do I tell Tomcat 9 to use a Postgres-specific object factory for producing DataSource object in response to JNDI query?
I have been wrestling Tomcat 9.0.26 to get its JNDI implementation to generate a `DataSource` whose implementation is the class `org.postgresql.ds.PGSimpleDataSource`, rather than the `org.apache.tomcat.dbcp.dbcp2.BasicDataSource` class obtained by default.
I am using the JNDI approach to externalize the database connection info (username, password, etc.) as a config file for Tomcat. That externalizing seems better for my needs than embedding those settings inside my web-app’s WAR file.
(1) Get the `postgresql-42.2.8.jar` on the appropriate class loader.
(2) Write an XML file for `<Context>` with a `factory` attribute for an implementation of `javax.naming.spi.ObjectFactory`.
(3) Place that context definition file in correct location.
(4) Register the `PGSimpleDataSource` class with Java’s Service Provider Interface (SPI).
(5) In my Java code, use a JNDI context to access a `DataSource` object.
—--—| Step 1 |———————
In my designated “base” folder for Tomcat, in a `lib` folder, place the `postgresql-42.2.8.jar` file.
Given Tomcat’s default `catalina.properties` file’s settings, that *base*/lib location leads to the JDBC driver being loaded in Tomcat’s “Common” class loader.
—--—| Step 2 |———————
Write an XML file named the same as my context. In this case: `clepsydra.xml` for a context named `clepsydra`.
That file’s content:
Note how I included an attribute for `factory` in that `Resource` definition. I am only guessing that is the right thing to do, based on my reading of the “Resource Definitions” section of the Tomcat page “The Context Container” at:
That page is confusing as it discusses only global resources, but I want this resource only for my own web-app, not globally.
My understanding is that `PGObjectFactory` implements `javax.naming.spi.ObjectFactory`. This means I should be able to tell Tomcat to use this object factory rather than Tomcat’s own object factory. The goal is to get at runtime a `org.postgresql.ds.PGSimpleDataSource` object rather than a `org.apache.tomcat.dbcp.dbcp2.BasicDataSource`.
—--—| Step 3 |———————
To place this XML file, I go to the `conf` folder I copied from Tomcat into my designated Tomcat “base” folder. In that `conf` folder, I create a `Catalina` folder for the name of the engine. Within that I create a folder named `localhost` for the name of the host, as I am running my Vaadin 14 web app from IntelliJ Ultimate edition 2019.3 externally in Tomcat 9.0.26 in macOS Mojave with Java 13.
I place my `clepsydra.xml` file in that *base*/conf/Catalina/localhost folder.
I know this context file is being loaded successfully because at runtime I am able to access the Environment entry seen above in the XML file:
—--—| Step 4 |———————
I am guessing that given the `.spi.` in `javax.naming.spi.ObjectFactory`, I need to register my desired `PGObjectFactory` via Java Service Provider Interface (SPI) facility.
So in my Vaadin app’s `resources` folder I create a `META-INF` folder. In there I create a `services` folder. In that `resources/META-INF/services` folder I create a file named exactly the name of the interface:
Inside that file I write one line, the name of the implementing class:
—--—| Step 5 |———————
After cleaning and rebuilding my project, at runtime I execute this code:
After all that effort, I get a null `DataSource`, with no Exception thrown. Perhaps there are error messages, but I do not see any on the console within IntelliJ. Is there somewhere else to look for error messages?
Can anyone tell me what I have missed, or what I am doing wrong?
I've added code tags to make your examples more readable. The Code button is part of the Ranch message editor, incidentally.
Your discussion is so long and complicated that I would have to print it out and read it carefully to fully understand it, and since I don't get paid for this, I'll give you the next best thing which is to work back from what you're apparently trying to do and explain it that way.
Enterprise Java supports JDBC Connection Pools. Connection Pools allow efficient use of database resources by keeping previously-created instances of Connection objects and handing them out on demand. The webapp(s) using that pool would acquire a Connection, use it to do database operations and then return it to the pool as soon as possible so that other users could make use of that Connection. Creating a Connection is expensive. Re-using an existing one is much more efficient.
PGSimpleDataSource is not a good choice here. It doesn't support pooling. You should just use the standard Tomcat dbcp connection pool datasource instead.
A common convention for a datasource is to define it to Tomcat with a JNDI relative name of something like "jdbc/mydata". Doesn't have to be done that way, but since Tomcat can hold lots of different types of things in its JNDI directories, it's a bit self-documenting. In your example, I see you used "jdbc/postgres". That's fine.
So in your webapp, to get a connection, you'd do a JNDI lookup to get the pool you defined under the name jdbc/postgres, cast it to a DataSource, then use the DataSource getConnection() method to get your Connection to the PostgreSQL database. That's all you need. All the extra stuff you covered is unnecessary. Again, once the app is done, return (close) the Connection as soon as possible and NEVER hold onto it between requests.
The connection pool isn't owned by the webapp. It's created and owned by Tomcat. It's possible, though not common to share a single pool across multiple webapps, since the Connections are used in isolation from each other even within a single webapp. That's why you need JNDI to find it for you.
Connection pools are the recommended way to access databases in almost all cases. It's generally not very efficient for a webapp to create its own Connections. Plus you end up having to hard-code things like user IDs, passwords, and connection URLs right into the WAR, which is a Bad Thing. Not only would it require making a separate copy of the WAR for testing and production (I HOPE you don't test using production databases!), it also makes it much easier for unauthorized people to find out userids/passwords.
Being persecuted doesn't in any way prove your righteousness or your beliefs. Many people get persecuted because they are repugnant or annoying. Or just because they can be.
Happiness is not a goal ... it's a by-product of a life well lived - Eleanor Roosevelt. Tiny ad: