Configure Apache Tomcat’s JNDI to return a `D ataSource` of class `PGSimpleDataSource`

Basil Bourque <[email protected]> Tue, 15 Oct 2019 14:16:26 -0700
Newsgroups gmane.comp.db.postgresql.jdbc
Message-ID <[email protected]>
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 the class =
`org.postgresql.ds.PGSimpleDataSource`, rather than the  =
`org.apache.tomcat.dbcp.dbcp2.BasicDataSource` class obtained by =
default.=20

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=E2=80=99s 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=E2=80=99s Service =
Provider Interface (SPI).
(5) In my Java code, use a JNDI context to access a `DataSource` object.

=E2=80=94--=E2=80=94|  Step 1  |=E2=80=94=E2=80=94=E2=80=94=E2=80=94=E2=80=
=94=E2=80=94=E2=80=94

In my designated =E2=80=9Cbase=E2=80=9D folder for Tomcat, in a `lib` =
folder, place the `postgresql-42.2.8.jar` file.=20

Given Tomcat=E2=80=99s default `catalina.properties` file=E2=80=99s =
settings, that *base*/lib location leads to the JDBC driver being loaded =
in Tomcat=E2=80=99s =E2=80=9CCommon=E2=80=9D class loader.

=E2=80=94--=E2=80=94|  Step 2  |=E2=80=94=E2=80=94=E2=80=94=E2=80=94=E2=80=
=94=E2=80=94=E2=80=94

Write an XML file named the same as my context. In this case: =
`clepsydra.xml` for a context named `clepsydra`.=20

That file=E2=80=99s content:

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

        <Resource
                    name=3D"jdbc/postgres"
                    auth=3D"Container"
                    type=3D"javax.sql.DataSource"
                    driverClassName=3D"org.postgresql.Driver"
                    url=3D"jdbc:postgresql://127.0.0.1:5432/mydb"
                    username=3D"myuser"
                    password=3D"mypasswd"
                    factory=3D"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 thing to do, based on =
my reading of the =E2=80=9CResource Definitions=E2=80=9D section of the =
Tomcat page =E2=80=9CThe Context Container=E2=80=9D at:

=
https://tomcat.apache.org/tomcat-9.0-doc/config/context.html#Resource_Defi=
nitions

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=E2=80=99s 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`.

=E2=80=94--=E2=80=94|  Step 3  |=E2=80=94=E2=80=94=E2=80=94=E2=80=94=E2=80=
=94=E2=80=94=E2=80=94

To place this XML file, I go to the `conf` folder I copied from Tomcat =
into my designated Tomcat =E2=80=9Cbase=E2=80=9D 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:

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

value: DEV

=E2=80=94--=E2=80=94|  Step 4  |=E2=80=94=E2=80=94=E2=80=94=E2=80=94=E2=80=
=94=E2=80=94=E2=80=94

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.=20

So in my Vaadin app=E2=80=99s `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:=20
javax.naming.spi.ObjectFactory

Inside that file I write one line, the name of the implementing class:=20=

org.postgresql.ds.common.PGObjectFactory

=E2=80=94--=E2=80=94|  Step 5  |=E2=80=94=E2=80=94=E2=80=94=E2=80=94=E2=80=
=94=E2=80=94=E2=80=94

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

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

value: null

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?

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

=E2=80=94Basil Bourque