November 01, 2015

Payara/GlassFish DataSource JNDI Lookup Reference

Introduction

This article is more of a reference than a how-to.  Setting up a JDBC Connection Pool and JDBC Resource in Payara/GlassFish is easy. However remembering how to do the JNDI lookups from your application is a little harder because, let's face it, we don't have to do it that often so its easy to forget.  So here is a reference.

Create "JDBC Connection Pool"

The first thing you'll need to do is create a "JDBC Connection Pool". As shown in Figure 1, Payara/GlassFish have a simple wizard for this. 

Figure 1: Wizard to start a new "JDBC Connection Pool"

PostgreSQL is my database of choice.  So during the setup I choose "Postgresql" and add the following configuration:

Datasource classname: org.postgresql.ds.PGSimpleDataSource
 
PropertyValue
userelephant
passwordjafj2r@#Rhh
serverNamelocalhost
portNumber5432
databaseNameemail

Remember, after creating a "JDBC Connection Pool", your application cannot use it because the pool has yet to be bound to JNDI.  To do the binding, let's look at the next step, which is to create a JDBC Resource.

Create "JDBC Resource"

This steps creates a JNDI binding for the "JDBC Connection Pool" so your application is able to perform a lookup and find it.  Payara/GlassFish again provides a nice simple wizard for you, and you start the wizard as shown in figure 2.

Figure 2: Wizard to start a new "JDBC Resource"

The Wizard can't get any simpler.  You need to supply a "JNDI Name"for the "JDBC Resource" and select a "JDBC Connection Pool" from the dropdown box.  That's it!  Well not quite.  Let's look at the "JNDI Name" value in a bit more detail.

JNDI can get really confusing.  In general, JNDI is a tree-like structure with a number of special contexts (java:global, java:app, java:module, java:comp) and some special paths (java:comp/env).  It gets even more confusing because in some cases a context - like java:comp/env - is assumed and prepended for you but in other cases it's not. So let's consider what "JNDI Name" really means for a "JDBC Resource" by looking at some examples.
 
JNDI Name@Resource(lookup = "%s")
OR
InitialContext.lookup("%s")
ResultComments
"EmailDS""EmailDS"SUCCESSLookup and "JNDI Name" match!
"EmailDS""/EmailDS"FAILSLookup does not match "JNDI Name"!  Lookup name has a leading "/" character!
"EmailDS""java:global/EmailDS"FAILSLookup does not match "JNDI Name"! Payara/Glassfish does not prepend the global context when binding to JNDI.
"EmailDS""java:comp/env/EmailDS"FAILSLookup does not match "JNDI Name"! The "java:comp/env" context and path is application specific.  We'll cover how to do this application specific configuration below.
"jdbc/app/EmailDS""jdbc/app/EmailDS"SUCCESSLookup and JNDI Name match!
"jdbc/app/EmailDS""/jdbc/app/EmailDS"FAILSLookup does not match "JNDI Name"!  Lookup name has a leading "/" character!
"jdbc/app/EmailDS""java:global/jdbc/app/EmailDS"FAILSLookup does not match "JNDI Name". Payara/Glassfish does not pre-pend the global context when binding to JNDI.
"jdbc/app/EmailDS""java:comp/env/jdbc/app/EmailDS"FAILSLookup does not match "JNDI Name". The "java:comp/env" context and path is application specific.  We'll cover how to do this application specific configuration below.

So, if your application has a Payara/GlassFish "JDBC Resource" and your code is performing a direct JNDI lookup, the above table should give you enough information to figure out what will succeed and what will fail.  When you create the "JDBC Resource", Payara/GlassFish will use the "JNDI Name" you give as the exact path in the JNDI tree to make the binding.  Payara/GlassFish does not prepend any contexts or paths onto the name you give.  So your code needs to do a lookup on the exact "JNDI Name" value.

Now you may be asking yourself, what about JNDI redirection?  It's always good practice to have your application JNDI lookup names to be decoupled from the real JNDI locations of the resources.  In other words, I want my application to lookup "java:module/env/jdbc/MyDS" but I want that lookup to be redirected/mapped to the real JNDI location - "EmailDS" - which in Payara/GlassFish is the "JNDI Name" value of the "JNDI Resource".  We'll take a look at how to do this next.


Redirection with web.xml and glassfish-web.xml

The web.xml and glassfish-web.xml files are used to accomplish JNDI redirection.  In general, inside of web.xml you specify what JNDI lookup your application will use to lookup a resource and then inside of glassfish-web.xml you map your application's lookup with the "JNDI Name" of the "JDBC Resource".  But with multiple context rules and automatic prepending of contexts, this too can get confusing so let's take a look at some examples.

Listing 1 shows a fully-qualified example.  In this example, the web.xml says the JNDI lookup used in your application is fully-qualified to "java:comp/email/database".  The glassfish-web.xml says "java:comp/email/database" is mapped to the real JNDI resource "EmailDS".

Listing 1: Fully-qualified redirect example

web.xml

  java:comp/email/database
  javax.sql.DataSource
    
glassfish-web.xml

  java:comp/email/database 
  EmailDS
    
For this example, let's consider some application lookups and see what happens.
 
@Resource(lookup = "%s")
OR
InitialContext.lookup("%s")
ResultComments
"java:comp/email/database"SUCCESSLookup matches what's in web.xml.  Successful redirection to real JNDI resource "EmailDS"
"java:comp/env/email/database"FAILSLookup does not match what's in web.xml!  Lookup has an "/env/" path which is not in web.xml.  Redirection fails.
"java:module/email/database"SUCCESSLookup does not match what's in web.xml, however, lookup succeeds because in a WAR the "java:comp" and "java:module" contexts are treated as the same thing for backward compatibility with previous EE versions. Successful redirection to real JNDI resource "EmailDS"
"EmailDS"SUCCESSRedirection avoided altogether, this is a direct lookup of the real JNDI resource.

Listing 2 shows a relative example.  In this example, the web.xml says the JNDI lookup used in your application is the relative name "Puppy".  The glassfish-web.xml says the relative name "Puppy" is mapped to the real JNDI resource "EmailDS".

Listing 2: Relative redirect example

web.xml

  Puppy
  javax.sql.DataSource
glassfish-web.xml

  Puppy 
  EmailDS

Now the big question is, this JNDI lookup name is relative to what?  Well, in this case it is relative to "java:module/env" or "java:comp/env" because remember in a WAR file the "java:comp" and "java:module" contexts are treated as the same thing for backward compatibility with previous EE versions.  These contexts are automatically prepended onto your relative name "Puppy".  Let's consider some application lookups and see what happens.
 
@Resource(lookup = "%s")
OR
InitialContext.lookup("%s")
ResultComments
"java:comp/env/Puppy"SUCCESSThe web.xml says "Puppy" which get's automatically prepended with "java:comp/env/".  Successful redirection to real JNDI resource "EmailDS"
"Puppy"FAILSLookup matchs what's in web.xml, but lookup forgot about the automatic context prepending.  Redirection fails.
"java:module/env/Puppy"SUCCESSThe web.xml says "Puppy" which get's automatically prepended with "java:comp/env/".  But remember, in a WAR the "java:comp" and "java:module" contexts are treated as the same thing for backward compatibility with previous EE versions. Successful redirection to real JNDI resource "EmailDS"
"EmailDS"SUCCESSRedirection avoided altogether, this is a direct lookup of the real JNDI resource.

So far, all these examples assume your application doing JNDI resource lookups itself.  However what if you are using a framework like JPA and the lookup of the JNDI resource is done for you?  What do you do then?  We'll look at this next.

A note about JPA persistence.xml
If you are using JPA, you specify the JNDI lookup using one of the "jta-data-source" tags like this:

EmailDS

However, the big question is what JNDI lookup string to use for this tag's value?  Well as far as I can make out, JPA does not use any of the redirection configured in web.xml and glassfish-web.xml.  Which means the value for <jta-data-source> must be the exact Payara/GlassFish "JNDI Name" value of the "JDBC Resource".  This of course couples the JPA configuration to the Payara/GlassFish EE server which may not be desirable.  Hopefully, this will be something the JPA expert group addresses in the next JPA spec update.

A note about @DataSourceDefinition
Java EE has the @DataSourceDefinition annotation which allows your application to create a data source by itself instead of relying on an EE server administrator to create the data source prior to your application's deployment.  This cuts down on some administration steps.  It has its advantages and disadvantages which I won't go into.  But what I do want to look at is how JNDI lookups work with a data source created by the @DataSourceDefinition annotation.

Redirection with web.xml and glassfish-web.xml
The @DataSourceDefinition annotation does not support redirection by web.xml and glassfish-web.xml.  If you think about it, this makes sense.  The @DataSourceDefinition is hard-coded in your source code so you know exactly what JNDI Name you used to create it so there is no need to redirect since the value is already in your code.
JPA data source 
There seemed to be a debate on whether or not @DataSourceDefinition support the JPA persistence.xml file.  The last word of the debate states it should be supported, that the JSR specification will be updated to make it more clear, and that a test will be added to the TCK to verify it. 
At this time (03 Nov 2015), in Payara/GlassFish, the @DataSourceDefinition annotation does not support the JPA persistence.xml file.  In other words, you cannot use the JNDI Name you used for the @DataSourceDefinition annotation in the JPA persistence.xml file.  I will continue to monitor this and will make an update to this article if anything changes.
Listing 3 shows a fully-qualified example.  In this example, the @DataSourceDefinition annotation has a JNDI Name which is fully qualified.  This exact, fully-qualified name, needs to be used in lookups in order for the lookup to succeed.

Listing 3: Fully-qualified @DataSourceDefinition example 
@DataSourceDefinition(
    name="java:global/jdbc/MyApplicationDS",
    className = "org.postgresql.ds.PGPoolingDataSource",
    url = "jdbc:postgresql://localhost:5432/email",
    user = "elephant",
    password = "jafj2r@#Rhh"
)

For this example, let's consider some application lookups and see what happens.
 
@Resource(lookup = "%s")
OR
InitialContext.lookup("%s")
ResultComments
"java:global/jdbc/MyApplicationDS"SUCCESSLookup matches what's in the @DataSourceDefinition#name annotation.
"java:global/env/jdbc/MyApplicationDS"FAILSLookup does not exactly match what's in the @DataSourceDefinition#name annotation.  The lookup has an "/env/" path which is not in the annotation

Listing 4 shows a relative example.  In this example, the @DataSourceDefinition annotation has a JNDI Name which is relative path.

Listing 4: Relative @DataSourceDefinition example

@DataSourceDefinition(
    name="MyApplicationDS",
    className = "org.postgresql.ds.PGPoolingDataSource",
    url = "jdbc:postgresql://localhost:5432/email",
    user = "elephant",
    password = "jafj2r@#Rhh"
)

Now the big question is, this JNDI lookup name is relative to what?  Well, in this case it is relative to "java:module/env" or "java:comp/env" because remember in a WAR file the "java:comp" and "java:module" contexts are treated as the same thing for backward compatibility with previous EE versions.  These contexts are automatically prepended onto your relative name "MyApplicationDS".  Let's consider some application lookups and see what happens.
 
@Resource(lookup = "%s")
OR
InitialContext.lookup("%s")
ResultComments
"java:comp/env/MyApplicationDS"SUCCESSThe @DataSourceDefinition says "MyApplicationDS" which get's automatically prepended with "java:comp/env/".  "
"MyApplicationDS"FAILSLookup matches what's in @DataSourceDefinition, but lookup forgot about the automatic context prepending.  So the lookup fails.
"java:module/env/MyApplicationDS"SUCCESSThe @DataSourceDefinition says "MyApplicationDS" which get's automatically prepended with "java:comp/env/".  But remember, in a WAR the "java:comp" and "java:module" contexts are treated as the same thing for backward compatibility with previous EE versions.

Finaly, listing 5 shows a special condition of a fully-qualified example.  In this example, the @DataSourceDefinition annotation has a JNDI Name which is fully qualified, but to special "java:comp/env/" context and path.

Listing 5: Special case fully-qualified @DataSourceDefinition example 
@DataSourceDefinition(
    name="java:comp/env/jdbc/WebAppDS",
    className = "org.postgresql.ds.PGPoolingDataSource",
    url = "jdbc:postgresql://localhost:5432/email",
    user = "elephant",
    password = "jafj2r@#Rhh"
)

For this example, let's consider some application lookups and see what happens.
 
@Resource(lookup = "%s")
OR
InitialContext.lookup("%s")
ResultComments
"java:comp/env/jdbc/WebAppDS"SUCCESSLookup matches what's in the @DataSourceDefinition#name annotation.
"java:module/env/jdbc/WebAppDS"SUCCESSLookup does not exactly match what's in the @DataSourceDefinition#name annotation.  The lookup uses the "module" context which is not in the annotation.  But remember, in a WAR the "java:comp" and "java:module" contexts are treated as the same thing for backward compatibility with previous EE versions.


That's it,
Enjoy!




No comments:

Post a Comment