Configure Apache Tomcat’s JNDI to return a `DataSource` of class `PGSimpleDataSource` - Mailing list pgsql-jdbc

From Basil Bourque
Subject Configure Apache Tomcat’s JNDI to return a `DataSource` of class `PGSimpleDataSource`
Date
Msg-id AD88A21D-F0A7-4966-BC22-080E4B9B42D0@gmail.com
Whole thread Raw
Responses Re: Configure Apache Tomcat’s JNDI to return a `DataSource` of class `PGSimpleDataSource`  (Dave Cramer <pg@fastcrypt.com>)
List pgsql-jdbc
Question

How do I tell Tomcat 9 to use a Postgres-specific object factory for producing DataSource object in response to JNDI
query?

Details

I have been wrestling Tomcat 9.0.26 to get its JNDI implementation to generate a `DataSource` whose implementation is
theclass `org.postgresql.ds.PGSimpleDataSource`, rather than the  `org.apache.tomcat.dbcp.dbcp2.BasicDataSource` class
obtainedby default.  

I am using the JNDI approach to externalize the database connection info (username, password, etc.) as a config file
forTomcat. That externalizing seems better for my needs than embedding those settings inside my web-app’s WAR file. 

My steps:

(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
loadedin 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:

    <?xml version="1.0" encoding="UTF-8"?>
    <Context>
        <!-- Domain: DEV, TEST, ACPT, ED, PROD  -->
        <Environment name = "work.basil.example.deployment-mode"
                     description = "Signals whether to run this web-app with development, testing, or production
settings."
                     value = "DEV"
                     type = "java.lang.String"
                     override = "false"
                     />

        <Resource
                    name="jdbc/postgres"
                    auth="Container"
                    type="javax.sql.DataSource"
                    driverClassName="org.postgresql.Driver"
                    url="jdbc:postgresql://127.0.0.1:5432/mydb"
                    username="myuser"
                    password="mypasswd"
                    factory="org.postgresql.ds.common.PGObjectFactory"
                    />
    </Context>


Note how I included an attribute for `factory` in that `Resource` definition. I am only guessing that is the right
thingto do, based on my reading of the “Resource Definitions” section of the Tomcat page “The Context Container” at: 

https://tomcat.apache.org/tomcat-9.0-doc/config/context.html#Resource_Definitions

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
tellTomcat 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`
forthe name of the host, as I am running my Vaadin 14 web app from IntelliJ Ultimate edition 2019.3 externally in
Tomcat9.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
abovein the XML file: 

    ctxInitial = new InitialContext();
    ctxEnv = ( Context ) ctxInitial.lookup( "java:comp/env" );
    String deploymentModeString = ( String ) ctxEnv.lookup( "work.basil.example.deployment-mode" );

value: DEV

—--—|  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:  
javax.naming.spi.ObjectFactory

Inside that file I write one line, the name of the implementing class:
org.postgresql.ds.common.PGObjectFactory

—--—|  Step 5  |———————

After cleaning and rebuilding my project, at runtime I execute this code:

    ctxInitial = new InitialContext();
    DataSource dataSource = ( DataSource ) ctxInitial.lookup( "java:comp/env/jdbc/postgres" );
    System.out.println( "dataSource = " + dataSource );

value: null

After all that effort, I get a null `DataSource`, with no Exception thrown. Perhaps there are error messages, but I do
notsee 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?

For another telling of this tale, see my Stack Overflow Question:
https://stackoverflow.com/q/58385528/642706

—Basil Bourque







pgsql-jdbc by date:

Previous
From: Dave Cramer
Date:
Subject: Re: Empty JAR file for builds jre7 and jre6 in version 42.2.8
Next
From: Dave Cramer
Date:
Subject: Re: Configure Apache Tomcat’s JNDI to return a `DataSource` of class `PGSimpleDataSource`