Forums de discussion

Liferay 7: can't access JNDI DataSource (they way it worked in 6.x)

Thomas Kellerer, modifié il y a 6 années.

Liferay 7: can't access JNDI DataSource (they way it worked in 6.x)

Expert Publications: 490 Date d'inscription: 09/06/08 Publications récentes
I am trying to migrate a portlet from Liferay 6.0 to Liferay 7.0

The portlet is using a JNDI DataSource configured in Tomcat's context.xml to access the database.
This is the same JNDI DataSource that Liferay itself is using through jdbc.default.jndi.name=jdbc/LiferayPool in portal-ext.properties

However my portlet code fails to retrieve the InitialContext with a "ClassNotFoundException":

The exception is:

16:31:09,788 FATAL [http-nio-8080-exec-8][DatabaseController:277] data source retrieval failed
javax.naming.NoInitialContextException: Cannot instantiate class: org.apache.naming.java.javaURLContextFactory [Root exception is java.lang.ClassNotFoundException: org.apache.naming.java.javaURLContextFactory cannot be found by my_portlet_1.0.0.SNAPSHOT]
        at javax.naming.spi.NamingManager.getInitialContext(NamingManager.java:674)
        at javax.naming.InitialContext.getDefaultInitCtx(InitialContext.java:313)
        at javax.naming.InitialContext.init(InitialContext.java:244)
        at javax.naming.InitialContext.<init>(InitialContext.java:192)
        at com.myportlet.DatabaseController.getConnection(DatabaseController.java:273)</init>


The code to access the datasource is pretty much standard:

public static Connection getConnection() {
    Connection result = null;
    DataSource dataSource = null;
    String dsname = null;

    try {
        // Retrieve the name of the JNDI Datasource from portal-ext.properties
        dsname = PropsUtil.get("jdbc.default.jndi.name");
        Context context = new InitialContext();
        log.debug("Using datasource: " + dsname);
        dataSource = (DataSource) context.lookup("java:comp/env/" + dsname);
    } catch (Exception e) {
        log.fatal("data source retrieval failed", e);
    }
    try {

        if (dataSource != null) {
            result = dataSource.getConnection();
        } else {
            log.fatal("Could not get the DataSource");
        }

        if (result != null) {
            result.setAutoCommit(false);
        } else {
            log.fatal("Could not obtain a connection");
        }
    } catch (SQLException e) {
        log.fatal("cannot connect to the db " + dsname, e);
    }
    return result;
}


So why can't that code create the InitialContext?

I assume this has something to do with the OSGi packaging, but I have no idea how to solve this.
Peter Helgren, modifié il y a 6 années.

RE: Liferay 7: can't access JNDI DataSource (they way it worked in 6.x)

Regular Member Publications: 124 Date d'inscription: 14/11/13 Publications récentes
I can't speak directly to your situation but my JNDI settings changed from 6.0.6 to 7.0. In 6.0.6 I had an entry in domain.xml (we used Glassfish) and then in ext-spring.xml as well. In Tomcat in 7.0 we now have entries in server.xml, context.xml and in the spring-ext.xml file in our service builder modules which is our primary use of the JNDI connection.

If you are interested I could pull sample entries from each file. I think I found most of the solutions on the LDN postings but I can't remember...
Thomas Kellerer, modifié il y a 6 années.

RE: Liferay 7: can't access JNDI DataSource (they way it worked in 6.x)

Expert Publications: 490 Date d'inscription: 09/06/08 Publications récentes
Peter Helgren:
If you are interested I could pull sample entries from each file. I think I found most of the solutions on the LDN postings but I can't remember...

That would be helpful, thanks.

Unfortunately, David's suggestion does not work as it tries to create a new DataSource rather then retrieving the one that is already uses by Liferay.
Peter Helgren, modifié il y a 6 années.

RE: Liferay 7: can't access JNDI DataSource (they way it worked in 6.x)

Regular Member Publications: 124 Date d'inscription: 14/11/13 Publications récentes
This applies to service builder modules using JNDI for connecting
In the Service Builder Module where the connection is being initiated ("Central_data" is my datasource reference in the service.xml entries):

in src/main/resources/META-INF/spring/spring-ext.xml:



<bean class="com.liferay.portal.dao.jdbc.spring.DataSourceFactoryBean" id="jndiDatasource">
      <property name="propertyPrefix" value="custom." />
     <property name="properties">
        <props>
               <prop key="custom.jndi.name">jdbc/extmemberdata</prop>
       </props>
     </property>
 </bean> 
 
 <bean class="org.springframework.jdbc.datasource.LazyConnectionDataSourceProxy" id="liferayDataSource">
      <property name="targetDataSource" ref="jndiDatasource" />
 </bean>
 <alias alias="Central_data&quot;" name="liferayDataSource" />



In context.xml in the TOMCAT_HOME/conf folder:

<resourcelink name="jdbc/extmemberdata" global="jdbc/extmemberdata" type="javax.sql.DataSource" />


In server.xml in TOMCAT_HOME/conf folder:

	<resource name="jdbc/extmemberdata" auth="Container" type="javax.sql.DataSource" factory="org.apache.tomcat.jdbc.pool.DataSourceFactory" username="myuser" password="mypassword
		driverClassName=" net.sourceforge.jtds.jdbc.Driver" url="jdbc:jtds:sqlserver://IP_ADDR/Central" maxActive="20" maxIdle="10" validationQuery="select 1" />



I set this up initially on my development PC (Windows) but it is also running on Linux in our staging environment. I am not sure if there are any additional entries needed if you are NOT using a service builder module. I hope this helps and doesn't confuse....
Thomas Kellerer, modifié il y a 6 années.

RE: Liferay 7: can't access JNDI DataSource (they way it worked in 6.x)

Expert Publications: 490 Date d'inscription: 09/06/08 Publications récentes
Peter Helgren:
This applies to service builder modules using JNDI for connecting
In the Service Builder Module where the connection is being initiated ("Central_data" is my datasource reference in the service.xml entries)

Thanks for the quick answer. Unfortunately that does not help me. I am neither using the ServiceBuilder nor Spring emoticon

I am now creating a DBCP2 BasicDataSource manually in my portlet code - there does not seem to be any other way with Liferay 7 as it effectively disabled most of the standard JavaEE APIs. Which is kind of stupid, because I now have to duplicate the connection information and bundle DBCP and the Postgres driver together with my portlets even though all that is already available. But before I waste more time on something that should be simple I am going to live with that solution.
thumbnail
David H Nebinger, modifié il y a 6 années.

RE: Liferay 7: can't access JNDI DataSource (they way it worked in 6.x)

Liferay Legend Publications: 14916 Date d'inscription: 02/09/06 Publications récentes
Thomas Kellerer:
So why can't that code create the InitialContext?


Why do things the hard way?

public DataSource getDataSource() {
    // fetch properties from portal-ext.properties like jdbc.mydb.jndi.name=MyJNDIDB
    Properties properties = PropsUtil.getProperties("mydb", true);
    // pass the properties to the data source factory to create an instance.
    return DataSourceFactoryUtil.initDataSource(properties);
}


This should work in your modules, no JNDI context stuff necessary. And if you don't want to use jndi, just use the regular properties like those found as jdbc.default.driverClassName, etc., just replace default with mydb.








Come meet me at the 2017 LSNA!
Thomas Kellerer, modifié il y a 6 années.

RE: Liferay 7: can't access JNDI DataSource (they way it worked in 6.x)

Expert Publications: 490 Date d'inscription: 09/06/08 Publications récentes
David H Nebinger:
Why do things the hard way?
public DataSource getDataSource() {
    // fetch properties from portal-ext.properties like jdbc.mydb.jndi.name=MyJNDIDB
    Properties properties = PropsUtil.getProperties("mydb", true);
    // pass the properties to the data source factory to create an instance.
    return DataSourceFactoryUtil.initDataSource(properties);
}


This should work in your modules, no JNDI context stuff necessary. And if you don't want to use jndi, just use the regular properties like those found as jdbc.default.driverClassName, etc., just replace default with mydb.

This doesn't work. DataSourceFactoryUtil.initDataSource apparently tries to create a new DataSource, not re-use an existing one that is defined in context.xml. When I try that code, I get an exception:

10:50:59,699 FATAL [http-nio-8080-exec-3][DatabaseController:272] Error retrieving DataSource connection
java.lang.ClassNotFoundException: com.zaxxer.hikari.HikariDataSource cannot be found by my-contact-portlet_1.0.0
        at org.eclipse.osgi.internal.loader.BundleLoader.findClassInternal(BundleLoader.java:444)
        at org.eclipse.osgi.internal.loader.BundleLoader.findClass(BundleLoader.java:357)
        at org.eclipse.osgi.internal.loader.BundleLoader.findClass(BundleLoader.java:349)
        at org.eclipse.osgi.internal.loader.ModuleClassLoader.loadClass(ModuleClassLoader.java:160)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
        at com.liferay.portal.dao.jdbc.DataSourceFactoryImpl.initDataSourceHikariCP(DataSourceFactoryImpl.java:326)
        at com.liferay.portal.dao.jdbc.DataSourceFactoryImpl.initDataSource(DataSourceFactoryImpl.java:171)
        at com.liferay.portal.kernel.dao.jdbc.DataSourceFactoryUtil.initDataSource(DataSourceFactoryUtil.java:44)


Which clearly indicates DataSourceFactoryUtil is not using the JNDI datasource defined in my Tomcat (which is used however by Liferay itself, so that sounds quite strange)

Why does Liferay 7 prevent me from using standard JavaEE interfaces?
Thomas Kellerer, modifié il y a 6 années.

RE: Liferay 7: can't access JNDI DataSource (they way it worked in 6.x)

Expert Publications: 490 Date d'inscription: 09/06/08 Publications récentes
A finally found an ugly (really: ugly) hack to work around this problem as none of the suggested solutions worked.

I create my own connection pool inside my portlet module.

I know this is ugly as hell, but as all my portlets are inside a single module I only need to do this once - so much for easy "modularization". If each of my portlets was in a separate module, I would have to create a connection pool manually for each and every portlet. Pretty stupid for an application running on a standard Java EE Servlet container that already manages all of that for me.
thumbnail
Vilmos Papp, modifié il y a 6 années.

RE: Liferay 7: can't access JNDI DataSource (they way it worked in 6.x)

Liferay Master Publications: 529 Date d'inscription: 21/10/10 Publications récentes
Hi Thomas,

Another way to do this probably to switch the ClassLoader to access the JNDI resource through the portal's class loader:

public static Connection getConnection() {
Connection result = null;
DataSource dataSource = null;
String dsname = null;
// Create reference for current thread
Thread thread = Thread.currentThread(); 
// Store the current classloader for later
ClassLoader origLoader = thread.getContextClassLoader();
try {
    

       // Retrieve the name of the JNDI Datasource from portal-ext.properties
       dsname = PropsUtil.get("jdbc.default.jndi.name");
       Context context = new InitialContext();
       // Change the current classloader to Portal's classloader:
       thread.setContextClassLoader(com.liferay.portal.kernel.util.PortalClassLoaderUtil.getClassLoader())
       log.debug("Using datasource: " + dsname);
       dataSource = (DataSource) context.lookup("java:comp/env/" + dsname);

        // Now it should be initiated:
        System.out.println("DataSource: " + dataSource.toString());
    } catch (Exception e) {
        log.fatal("data source retrieval failed", e);
   }
finally {
    // switch back to the original classloader
    thread.setContextClassLoader(origLoader);
}
    try {

        if (dataSource != null) {
            result = dataSource.getConnection();
        } else {
           log.fatal("Could not get the DataSource");
        }

        if (result != null) {
           result.setAutoCommit(false);
       } else {
           log.fatal("Could not obtain a connection");
        }
    } catch (SQLException e) {
       log.fatal("cannot connect to the db " + dsname, e);
   }
    return result;
}


Hope this helps.
Bogdan Bilcan, modifié il y a 6 années.

RE: Liferay 7: can't access JNDI DataSource (they way it worked in 6.x)

New Member Publications: 2 Date d'inscription: 15/11/17 Publications récentes
Hi Vilmos,

I have a similar situation and the code i was using gives me the same error about "org.apache.naming.java.javaURLContextFactory cannot be found by com.test.custom.dbmodule_1.0.0"
Can you please, provide a sample code with all the settings made for the method that you posted to work?
Right now i have in portal-ext.properties added a second entry for the external db like this:

########### External DB Oracle Database Configuration ##########
jdbc.external.driverClassName=oracle.jdbc.OracleDriver
jdbc.external.url=jdbc:oracle:thin:@<host>:<port>:<SID>
jdbc.external.username=x
jdbc.external.password=x

and i also put in context.xml an resource like so:

<Resource name="jdbc/MyDataSource"
auth="Container"
type="javax.sql.DataSource"
maxActive="100" maxIdle="30" maxWait="10000"
username="x" password="x"
driverClassName="oracle.jdbc.OracleDriver"
url="jdbc:oracle:thin:@<host>:<port>:<SID>"/>

I saw that in your method you are reading the default connection details from portal-ext.properties. Wouldn't that make a connection to the default Liferay DB?

Thank you,