Blogs

Blogs

Setting Up JNDI in Liferay 7.4

Hey, so a quick post today about how to set up JNDI connections in Liferay CE 7.4 or Liferay DXP 7.4.

Liferay moved jars around in 7.4. No longer are there key jars in the Tomcat lib/ext directory (or for other application servers).

To set up JNDI resources under 7.4, you'll need to put the necessary JDBC jars into the Tomcat lib directory (or the lib directory of your application server). In my example below I'm using an Oracle database, so I needed to copy the ojdbc8.jar into the tomcat-9.0.56/lib directory.

This applies for any JDBC jar, including MySQL and Postgres; just because Liferay is able to connect using the properties, this does not mean that Tomcat would have access to the jars for a JNDI connection.

If you are going to use the Hikari Connection Pool, you need to copy the hikaricp jar and an slf4j-api jar (a dependency of Hikari CP) also to the Tomcat lib directory.

You should note that there is no longer OOTB support for a lib/ext directory like Liferay used in previous releases, so you can't just copy your old lib/ext directory to the new bundle, it's not going to work.

With these in place, you can define your JNDI resources.

I tend to put mine into the Tomcat conf/Catalina/localhost/ROOT.xml like follows:

<?xml version="1.0" encoding="UTF-8"?>

<Context crossContext="true">
	<JarScanner className="com.liferay.support.tomcat.util.scan.NOPJarScanner" />

	<Manager pathname="" />

	<Resource name="jdbc/liferay" auth="Container"
		factory="com.zaxxer.hikari.HikariJNDIFactory"
		type="javax.sql.DataSource"
		minimumIdle="5" 
		maximumPoolSize="10"
		connectionTimeout="300000"
		driverClassName="oracle.jdbc.OracleDriver"
		jdbcUrl="jdbc:oracle:thin:@192.168.1.213:1521/liferay"
		dataSource.user="liferay"
	dataSource.password="password" />
</Context>

Then in portal-ext.properties I just need to use the JNDI reference: jdbc.default.jndi.name=jdbc/liferay.

And, if using external databases, you still need to use the portal class loader as demonstrated here: https://help.liferay.com/hc/en-us/articles/360029316251-Connecting-to-JNDI-Data-Sources but slightly updated per below...

Hopefully this helps with your 7.4 JNDI setup!

Update 02/23/2022...

You'll see in the comments where Matthias Bläsing was having an issue with trying to access JNDI, and actually the article that I linked is partially the right solution for 7.4, but not completely as I had originally posted.

In 7.4 Liferay introduced the "Shielded Class Loader" in an effort to shield the webapp class loader (the guy that Tomcat sets up for us and knows about the JNDI artifacts) from the OSGi container.

In doing so, this actually breaks the Liferay class loader trick because PortalClassLoaderUtil.getClassLoader() returns the shielded class loader, not the webapp class loader.

To access the webapp class loader, we have to go a bit further... Here's the Liferay example, but fixed to deal with the shielded class loader:

Thread thread = Thread.currentThread();

// Get the thread's class loader. You'll reinstate it after using
// the data source you look up using JNDI
ClassLoader origLoader = thread.getContextClassLoader();

// get the shielded class loader
ClassLoader shieldedClassLoader = PortalClassLoaderUtil.getClassLoader();

// get the webapp class loader from it
ClassLoader webappClassLoader = shieldedClassLoader.getClass().getClassLoader();

// Set webapp class loader on the thread
thread.setContextClassLoader(webappClassLoader);

try {
  // Look up the data source and connect to it

  InitialContext ctx = new InitialContext();
  DataSource datasource = (DataSource) ctx.lookup("java:comp/env/jdbc/TestDB");

  Connection connection = datasource.getConnection();
  Statement statement = connection.createStatement();

  // Execute SQL statements here ...

  connection.close();
} catch (NamingException ne) {
  ne.printStackTrace();
} catch (SQLException sqle) {
  sqle.printStackTrace();
} finally {
  // Switch back to the original context class loader
  thread.setContextClassLoader(origLoader);
}

We're still switching around the class loaders, but now we have to use the class loader for the shielded class loader class, as this comes from the webapp class loader, and not the Liferay class loader.

It's an extra couple of steps, but it should work to get to the JNDI resources you need...

Enjoy!

Thank you, this is just what I needed! We've been having issues using JNDI in our local testing of 7.4.13-ga1. I was able to leverage my existing setup (context.xml) and fix this just by changing the location where I was copying the mysql jdbc jar into the container from TOMCAT_HOME/lib/ext to TOMCAT_HOME/lib.

We are currently trying to migrate from 7.3.X to 7.4 and the first smoke test already showed serious problems, as the JNDI setup seems to be broken (a JNDI datasource for Liferay works, but not JNDI Datasources in portlets). We use hibernate and the tomcat connection pools and these don't work anymore. This seems to be a known issue:

https://issues.liferay.com/browse/LPS-138890

We are willing to rework the shared connection pool integration, but I came up empty. Ideas welcome.

Answering myself, we are experimenting with providing the connetion pools from a JNDI module. While the JNDI environment of tomcat is invisible to the portlets, a JNDI environment is still present.

Our module expects HikariCP to be provided from the OSGI environment and uses an OSGI @Component(immediate=true). In the activator an InitialContext is created, the "java:/comp/env/jdbc" subcontext is created and based on a property file entries for DB connections are created using Hikari. These entries are then visible to other modules (including portlets) and can be queried by them via JNDI.