Message Boards

JNDI to external database - one more time

thumbnail
Pete Helgren, modified 4 Years ago.

JNDI to external database - one more time

Regular Member Posts: 225 Join Date: 4/7/11 Recent Posts
Been around the block a few times on this and I'd really like to be done with it and move on.    I am *close* to getting my 7.0 SB artifacts to 7.3 but I am stuck on the step needed to use JNDI as the connection method.  This was SO easy in 7.0 and before so I don't know exactly what is missing.   I have followed a couple of different links in 7.1/ 7.2 documentation that I haven't had success with (nothing for 7.3 but I don't think there are changes in the way to connect using JNDI).  There is this article   that follows the pattern I was using in 7.0.  And there is this article that is 7.2 specific but is without much context to understand it.  There is this post where David Nebinger gives the answer, but without example, so trying to implement the solution is a bit of a challenge.

David says "The only caveat is that you need to set the class loader to the portal's class loader before you invoke the JNDI lookup."  Hmmm.  OK, how do I do that?  Or *where* do I do that?    And, to add further content, there is also this sample in github that adds example without much detail as to why..
I do believe the earlier article I mentioned here  IS the one that David was probably referring to but again, no mention of exactly  where the code snippet is to be used or how it is implemented.  My hope it is a one-off change...somewhere. But where the code actually goes is the mystery.
If it is of any value, the SB modules (about 20 of them) I am using are not really accessed by a portlet in Liferay.  They are mostly used as JSON API services access from external sources. 
I just need a hint or two on how to implement "set the class loader to the portal's class loader before you invoke the JNDI lookup".  Where does the code go?  I assume in one of the implementation classes?  ALL of the implementation classes ?(hope not)....
thumbnail
David H Nebinger, modified 4 Years ago.

RE: JNDI to external database - one more time

Liferay Legend Posts: 14919 Join Date: 9/2/06 Recent Posts
Sorry, Pete, sometimes when I answer I can't include all of the details cuz I'll often answer on the road or something where I don't have them handy...

Sure, so https://portal.liferay.dev/docs/7-2/appdev/-/knowledge_base/a/connecting-to-data-sources-using-jndi is the model for doing a JNDI lookup, or really doing anything that requires invoking in the web application class loader's context in general, so other examples might be to reference Tomcat exposed APIs or those of some other application container.

As to how to use it, well, that depends upon where you need it I think. When you are doing manual lookups, well then it is a matter of wrapping all of the shared code into a method with this signature such as public DataSource getMyDataSource() and the body of the method will end up returning the DS you've pulled.

In Service Builder with Spring as the dependency injector though, things get a little hairy. The 7.0 reference you're following fails because all the DataSourceFactoryBean does is try to invoke the straight up JNDI lookup w/o changing the thread context class loader.

So I would actually recommend not using Liferay's DataSourceFactoryBean, but to create your own version and copy Liferay's as your starting point. It's really a simple class to copy, so it won't hurt to use your modified version.

Instead of the simple line that Liferay's version uses here: https://github.com/liferay/liferay-portal/blob/master/portal-impl/src/com/liferay/portal/dao/jdbc/spring/DataSourceFactoryBean.java#L48 you'll actually use the code to set up and tear down the ThreadContextClassLoader before fetching the datasource from the DataSourceFactoryUtil call.

So you end up with something like this in at the end of your createInstance() method:


DataSource dataSource = null;

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();

// Set Liferay's class loader on the thread

thread.setContextClassLoader(PortalClassLoaderUtil.getClassLoader());

try {
  dataSource = DataSourceFactoryUtil.initDataSource(properties);
} catch (NamingException ne) {
  ne.printStackTrace();
} catch (SQLException sqle) {
  sqle.printStackTrace();
} finally {
  // Switch back to the original context class loader

  thread.setContextClassLoader(origLoader);
}

return datasource;


This will give you the right context to create the JNDI connection but still rely on the bulk of Liferay code to get the job done. In your ext-spring.xml file, you'll have your bean definition:

<bean class="com.example.MyDataSourceFactoryBean" id="liferayDataSourceFactory">
    <property name="propertyPrefix" value="custom." />
    <property name="properties">
      <props>
        <prop key="custom.jndi.name">jdbc/externalDataSource</prop>
      </props>
    </property>
  </bean>
In this combination, you should have everything you need.

Now, if you are trying to use the DS dependency injection (the default for new SB modules), you're kind of out of luck.

Last I heard there was a bug that was interfering with wiring external databases into a SB module using the DS form of DI. I know a fix was either being worked on or was being released, but I don't remember the status on it.

All I know is that if I were going to be wiring up to an external datasource, I'd be sticking with the Spring DI because that should still just work.
jeff hatlestad, modified 4 Years ago.

RE: JNDI to external database - one more time

New Member Posts: 7 Join Date: 3/1/20 Recent Posts
Hey David,
Is this true for 7.2? I see his question was regarding 7.3. My company is upgrading from 7.0 to 7.2. Im in charge of upgrading one of our applications. I did not code the application. I have a good build but when I try to use the app in liferay Im getting similar error. It works perfectly with 7.0. 
My ext-spring.xml
<!--?xml version="1.0"?-->

<beans default-destroy-method="destroy" default-init-method="afterPropertiesSet" xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemalocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">

   <!-- To define an external data source, the liferayDataSource Spring bean
       must be overridden. Other default Spring beans like liferaySessionFactory
       and liferayTransactionManager may optionally be overridden.

       liferayDataSourceFactory refers to the data source configured on the
       application server. -->
  <bean class="com.liferay.portal.dao.jdbc.spring.DataSourceFactoryBean" id="liferayDataSourceFactory">
      <property name="propertyPrefix" value="jdbc.ext." />
      <property name="properties">
          <props>
              <prop key="jdbc.ext.jndi.name">jdbc/AppProfile</prop>
          </props>
      </property>
  </bean>


   <!-- The data source bean refers to the factory to access the data source.
   -->
   <bean class="org.springframework.jdbc.datasource.LazyConnectionDataSourceProxy" id="liferayDataSource">
       <property name="targetDataSource" ref="liferayDataSourceFactory" />
   </bean>

   <!-- In service.xml, we associated our entity with the extDataSource. To
       associate the extDataSource with our overridden liferayDataSource, we define
       this alias. -->
   <alias alias="extDataSource" name="liferayDataSource" />
</beans>


My stacktrace:2020-03-01 01:22:26.072 WARN  [Portal Dependency Manager Component Executor--1][ModuleApplicationContext:551] Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'liferayDataSource' defined in URL [bundleentry://7963.fwk452531310/META-INF/spring/ext-spring.xml]: Cannot resolve reference to bean 'liferayDataSourceFactory' while setting bean property 'targetDataSource'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'liferayDataSourceFactory' defined in URL [bundleentry://7963.fwk452531310/META-INF/spring/ext-spring.xml]: Invocation of init method failed; nested exception is java.lang.NullPointerException
2020-03-01 01:22:26.082 ERROR [Portal Dependency Manager Component Executor--1][LogService:93] [Portal Dependency Manager Component Executor--1] Invocation of 'start' failed. 
java.lang.Exception: Unable to start org.lvhn.appProfile.service
    at com.liferay.portal.spring.extender.internal.context.ModuleApplicationContextRegistrator.start(ModuleApplicationContextRegistrator.java:107)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.apache.felix.dm.impl.InvocationUtil.invokeMethod(InvocationUtil.java:157)
    at org.apache.felix.dm.impl.InvocationUtil.invokeCallbackMethod(InvocationUtil.java:112)
    at org.apache.felix.dm.impl.ComponentImpl.invokeCallbackMethod(ComponentImpl.java:638)
    at org.apache.felix.dm.impl.ComponentImpl.invoke(ComponentImpl.java:1783)
    at org.apache.felix.dm.impl.ComponentImpl.invokeStart(ComponentImpl.java:1281)
    at org.apache.felix.dm.impl.ComponentImpl.performTransition(ComponentImpl.java:1232)
    at org.apache.felix.dm.impl.ComponentImpl.handleChange(ComponentImpl.java:1166)
    at org.apache.felix.dm.impl.ComponentImpl.lambda$start$2(ComponentImpl.java:502)
    at org.apache.felix.dm.impl.DispatchExecutor.runTask(DispatchExecutor.java:182)
    at org.apache.felix.dm.impl.DispatchExecutor.run(DispatchExecutor.java:165)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
    at java.lang.Thread.run(Thread.java:748)
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'liferayDataSource' defined in URL [bundleentry://7963.fwk452531310/META-INF/spring/ext-spring.xml]: Cannot resolve reference to bean 'liferayDataSourceFactory' while setting bean property 'targetDataSource'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'liferayDataSourceFactory' defined in URL [bundleentry://7963.fwk452531310/META-INF/spring/ext-spring.xml]: Invocation of init method failed; nested exception is java.lang.NullPointerException
    at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveReference(BeanDefinitionValueResolver.java:359)
    at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveValueIfNecessary(BeanDefinitionValueResolver.java:108)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyPropertyValues(AbstractAutowireCapableBeanFactory.java:1534)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1281)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:551)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:481)
    at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:312)
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:230)
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:308)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202)
    at com.liferay.portal.spring.aop.AopConfigurableApplicationContextConfigurator$AopBeanFactoryPostProcessor._getPlatformTransactionManager(AopConfigurableApplicationContextConfigurator.java:189)
    at com.liferay.portal.spring.aop.AopConfigurableApplicationContextConfigurator$AopBeanFactoryPostProcessor.postProcessBeanFactory(AopConfigurableApplicationContextConfigurator.java:123)
    at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(PostProcessorRegistrationDelegate.java:283)
    at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(PostProcessorRegistrationDelegate.java:128)
    at org.springframework.context.support.AbstractApplicationContext.invokeBeanFactoryPostProcessors(AbstractApplicationContext.java:687)
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:525)
    at com.liferay.portal.spring.extender.internal.context.ModuleApplicationContextRegistrator.start(ModuleApplicationContextRegistrator.java:89)
    ... 17 more
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'liferayDataSourceFactory' defined in URL [bundleentry://7963.fwk452531310/META-INF/spring/ext-spring.xml]: Invocation of init method failed; nested exception is java.lang.NullPointerException
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1631)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:553)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:481)
    at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:312)
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:230)
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:308)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:197)
    at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveReference(BeanDefinitionValueResolver.java:351)
    ... 33 more
Caused by: java.lang.NullPointerException
    at java.lang.Class.forName0(Native Method)
    at java.lang.Class.forName(Class.java:264)
    at com.liferay.portal.dao.jdbc.DataSourceFactoryImpl.testDatabaseClass(DataSourceFactoryImpl.java:556)
    at com.liferay.portal.dao.jdbc.DataSourceFactoryImpl.initDataSource(DataSourceFactoryImpl.java:128)
    at com.liferay.portal.kernel.dao.jdbc.DataSourceFactoryUtil.initDataSource(DataSourceFactoryUtil.java:39)
    at com.liferay.portal.dao.jdbc.spring.DataSourceFactoryBean.createInstance(DataSourceFactoryBean.java:44)
    at com.liferay.portal.dao.jdbc.spring.DataSourceFactoryBean.createInstance(DataSourceFactoryBean.java:30)
    at org.springframework.beans.factory.config.AbstractFactoryBean.afterPropertiesSet(AbstractFactoryBean.java:135)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1689)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1627)
    ... 40 more

Can I use the same fix you had above?
ThanksJEff
thumbnail
David H Nebinger, modified 4 Years ago.

RE: JNDI to external database - one more time

Liferay Legend Posts: 14919 Join Date: 9/2/06 Recent Posts
Yes, Jeff, the same code should also be good for 7.2. Actually I haven't tested directly from 7.3, I did my sample after looking at 7.2's implementation.
thumbnail
Pete Helgren, modified 4 Years ago.

RE: JNDI to external database - one more time

Regular Member Posts: 225 Join Date: 4/7/11 Recent Posts
Thanks David (I replied direct by email and it bounced...)  In any case, I seethe  basics, and I was on the right track.  But here is the thing where I am "stuck" and need a bit more hand holding (Jeff you can jump in if you know the answer as well).  My goal of course is to make the modification and then have it work for all my SB modules that use JNDI.  If I modify and build, where does the class get deployed to?  Your recommendation is "So I would actually recommend not using Liferay's DataSourceFactoryBean, but to create your own version and copy Liferay's as your starting point. It's really a simple class to copy, so it won't hurt to use your modified version."  So...copied, modified, compiled and deployed where?  I don't think I have modified Liferay code before so I need to know where to put it so that it will be accessible to the modules that need it.

​​​​​​​Thanks again.
jeff hatlestad, modified 4 Years ago.

RE: JNDI to external database - one more time

New Member Posts: 7 Join Date: 3/1/20 Recent Posts
Hey Pete, I was going to put it into my service module. Thats where my ext-spring.xml resides. Here is my datasource factory. Is this correct? For the life of me I cannt seem to get rid of a compile error for :

import org.springframework.beans.factory.config.AbstractFactoryBean;

cannot find symbol class AbstractFactoryBean

method does not override or implement a method from a supertype



These three errors I cant seem to get rid of. I added dependicies for that module using spring-beans-5.2.4.RELEASE.jar but I feel like Im missing something here.; I added jar files to lib folder, Tomcat folders, etc. Please any help would be appreciated. One thing I have to do, not that it should make a difference. but Im using Intellij which Im not used to. Im an eclipse guy.
package org.lvhn.appProfile.jdbc;

import com.liferay.portal.kernel.dao.jdbc.DataSourceFactoryUtil;
import com.liferay.portal.kernel.util.PortalClassLoaderUtil;
import com.liferay.portal.kernel.util.PropertiesUtil;
import com.liferay.portal.kernel.util.PropsUtil;
import org.springframework.beans.factory.config.AbstractFactoryBean;

import javax.naming.NamingException;
import javax.sql.DataSource;
import java.sql.SQLException;
import java.util.Properties;
public class AppProfileDataSourceFactoryBean extends AbstractFactoryBean<datasource> {

    private DataSource datasource;

    @Override
    protected DataSource createInstance() throws Exception {
        if (_dataSource != null) {
            return _dataSource;
        }

        Properties properties = _properties;

        if (properties == null) {
            properties = PropsUtil.[i]getProperties[/i](_propertyPrefix, true);
        }
        else {
            properties = PropertiesUtil.[i]getProperties[/i](
                    properties, _propertyPrefix, true);
        }


        DataSource dataSource = null;

        Thread thread = Thread.[i]currentThread[/i]();

// Get the thread's class loader. You'll reinstate it after using
// the data source you look up using JNDI

        ClassLoader origLoader = thread.getContextClassLoader();

// Set Liferay's class loader on the thread

        thread.setContextClassLoader(PortalClassLoaderUtil.[i]getClassLoader[/i]());

        try {
            dataSource = DataSourceFactoryUtil.[i]initDataSource[/i](properties);
        } catch (NamingException ne) {
            ne.printStackTrace();
        } catch (SQLException sqle) {
            sqle.printStackTrace();
        } finally {
            // Switch back to the original context class loader

            thread.setContextClassLoader(origLoader);
            return datasource;
        }

    }

        @Override
        public void destroyInstance (DataSource dataSource) throws Exception {
            DataSourceFactoryUtil.[i]destroyDataSource[/i](dataSource);
        }

        @Override
        public Class<datasource> getObjectType () {
            return DataSource.class;
        }

        public void setDataSource (DataSource dataSource){
            _dataSource = dataSource;
        }

        public void setProperties (Properties properties){
            _properties = properties;
        }

        public void setPropertyPrefix (String propertyPrefix){
            _propertyPrefix = propertyPrefix;
        }

        public void setPropertyPrefixLookup (String propertyPrefixLookup){
            _propertyPrefix = PropsUtil.[i]get[/i](propertyPrefixLookup);
        }

        private DataSource _dataSource;
        private Properties _properties;
        private String _propertyPrefix;

    }
</datasource></datasource>
thumbnail
David H Nebinger, modified 4 Years ago.

RE: JNDI to external database - one more time

Liferay Legend Posts: 14919 Join Date: 9/2/06 Recent Posts
I would almost recommend following Jeff's example and embedding it into the SB module, even if you have multiples.


I know it seems like code smell to copy the class around, but this way each one will always work independently of each other and not have an additional dependency.

If you did want to share it, put the class by itself into a module jar project (I use API blade templates for things like this) and export the package it is in in your bnd.bnd file so it is available for all. This will share, but it adds another dependency your modules would need to launch and honestly I'm not 100% sure how referencing the class in a spring xml file from another module will really work. This would need to be tested out and verified before I'd want to count on it.
jeff hatlestad, modified 4 Years ago.

RE: JNDI to external database - one more time

New Member Posts: 7 Join Date: 3/1/20 Recent Posts
Thanks for all the help David. Any thoughts on my 
package org.springframework.beans.factory.config.AbstractFactoryBean; does not exist. I cant seem to fix this issue. Am I not including the correct jar or including it properly?

Appreciate it.
thumbnail
David H Nebinger, modified 4 Years ago.

RE: JNDI to external database - one more time

Liferay Legend Posts: 14919 Join Date: 9/2/06 Recent Posts
Sorry, missed that part...

So you do want to include the necessary spring jars, but try to use the same version as the portal, don't jump on spring 5 just because it's there. For 7.2, I'm seeing 4.3.22 as the version. You'll need to compileInclude spring-core and spring-beans, but I think that is it.

When you use the portal's class, that you get through portal-kernel so spring dependencies don't bleed into your module, but since you're extending AbstractFactoryBean directly, you'll need to satisfy the dependencies yourself.
jeff hatlestad, modified 4 Years ago.

RE: JNDI to external database - one more time

New Member Posts: 7 Join Date: 3/1/20 Recent Posts
Any ideas on whats causing this one. Im getting further but it doesnt seem to like liferayDataSource from the ext-spring.xml:


class="org.springframework.jdbc.datasource.LazyConnectionDataSourceProxy"
id="liferayDataSource">
<property name="targetDataSource" ref="liferayDataSourceFactory" />
</bean>

<!-- In service.xml, we associated our entity with the extDataSource. To
associate the extDataSource with our overridden liferayDataSource, we define
this alias. -->
<alias alias="extDataSource" name="liferayDataSource" />



Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'liferayDataSource' defined in URL [bundleentry://10245.fwk1734075198/META-INF/spring/ext-spring.xml]: Error setting property values; nested exception is org.springframework.beans.PropertyBatchUpdateException; nested PropertyAccessExceptions (1) are:_PropertyAccessException 1: org.springframework.beans.MethodInvocationException: Property 'targetDataSource' threw exception; nested exception is java.lang.IllegalArgumentException: 'targetDataSource' must not be null [Sanitized]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyPropertyValues(AbstractAutowireCapableBeanFactory.java:1571)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1281)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:551)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:481)
    at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:312)
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:230)
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:308)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202)
    at com.liferay.portal.spring.aop.AopConfigurableApplicationContextConfigurator$AopBeanFactoryPostProcessor._getPlatformTransactionManager(AopConfigurableApplicationContextConfigurator.java:189)
    at com.liferay.portal.spring.aop.AopConfigurableApplicationContextConfigurator$AopBeanFactoryPostProcessor.postProcessBeanFactory(AopConfigurableApplicationContextConfigurator.java:123)
    at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(PostProcessorRegistrationDelegate.java:283)
    at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(PostProcessorRegistrationDelegate.java:128)
    at org.springframework.context.support.AbstractApplicationContext.invokeBeanFactoryPostProcessors(AbstractApplicationContext.java:687)
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:525)
    at com.liferay.portal.spring.extender.internal.context.ModuleApplicationContextRegistrator.start(ModuleApplicationContextRegistrator.java:89)
    ... 18 more
Caused by: org.springframework.beans.PropertyBatchUpdateException; nested PropertyAccessExceptions (1) are:_PropertyAccessException 1: org.springframework.beans.MethodInvocationException: Property 'targetDataSource' threw exception; nested exception is java.lang.IllegalArgumentException: 'targetDataSource' must not be null [Sanitized]
    at org.springframework.beans.AbstractPropertyAccessor.setPropertyValues(AbstractPropertyAccessor.java:121)
    at org.springframework.beans.AbstractPropertyAccessor.setPropertyValues(AbstractPropertyAccessor.java:75)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyPropertyValues(AbstractAutowireCapableBeanFactory.java:1567)
    ... 32 more
jeff hatlestad, modified 4 Years ago.

RE: JNDI to external database - one more time

New Member Posts: 7 Join Date: 3/1/20 Recent Posts
Plus still cant resolve this:
org.springframework.jdbc.datasource.LazyConnectionDataSourceProxy

exists in the ext-spring.xml
thumbnail
David H Nebinger, modified 4 Years ago.

RE: JNDI to external database - one more time

Liferay Legend Posts: 14919 Join Date: 9/2/06 Recent Posts
okay, so clearly I thought this was going to be much easier than what it has turned out to be...

Let me work on an example and try to get it up and running; I'll get something working for 7.2 and that should extend to 7.3...  I'll keep you updated on my progress...
jeff hatlestad, modified 4 Years ago.

RE: JNDI to external database - one more time

New Member Posts: 7 Join Date: 3/1/20 Recent Posts
Thanks so much David. I really appreciate you taking the time to help. 
thumbnail
Pete Helgren, modified 4 Years ago.

RE: JNDI to external database - one more time

Regular Member Posts: 225 Join Date: 4/7/11 Recent Posts
"okay, so clearly I thought this was going to be much easier than what it has turned out to be..."   Thanks for that.  I thought it would be easy OR it would get solved/bug fixed quickly since I am guessing that there are plenty of folks who use JNDI with their SB modules.  So, I look forward to seeing how this progresses...sounds like it would be a great blog post.
jeff hatlestad, modified 4 Years ago.

RE: JNDI to external database - one more time

New Member Posts: 7 Join Date: 3/1/20 Recent Posts
Hey Pete, looks like here is a fix in
https://help.liferay.com/hc/en-us/requests/21250
thumbnail
Pete Helgren, modified 4 Years ago.

RE: JNDI to external database - one more time

Regular Member Posts: 225 Join Date: 4/7/11 Recent Posts
jeff hatlestad:

Hey Pete, looks like here is a fix in
https://help.liferay.com/hc/en-us/requests/21250

Jeff are you a DXP user?  Is this working for you now...still stuck in 7.3 CE
thumbnail
Pete Helgren, modified 4 Years ago.

RE: JNDI to external database - one more time

Regular Member Posts: 225 Join Date: 4/7/11 Recent Posts
Jeff,
You must have some special access because I get a page not found on that link.  Not sure how to get to that request. Maybe you have DXP?  I only have the CE version 
thumbnail
Pete Helgren, modified 3 Years ago.

RE: JNDI to external database - one more time SOLVED

Regular Member Posts: 225 Join Date: 4/7/11 Recent Posts
I have finally solved the issue with my JNDI connections and the solution was to NOT use JNDI for the connections.  The explanation on how to use JNDI in LR 7.3 found here: https://help.liferay.com/hc/en-us/articles/360029316251-Connecting-to-JNDI-Data-Sources  and the sample code tutorial found here: https://help.liferay.com/hc/en-us/articles/360029005912-Service-Builder-Application-Using-External-Database-via-JNDI  wasn't worthless but wasn't helpful either.  Using both those documents and other info found on the Internet, I was never able to get a JNDI connection to work.  I continued to get javax.naming.NoInitialContextException: Cannot instantiate class: org.apache.naming.java.javaURLContextFactory [Root exception is java.lang.ClassNotFoundException: org.apache.naming.java.javaURLContextFactory] and although the instructions say to use the LR DXP classloader, without much assistance and with the poor documentation, I never got it to work.
So I went back to the other approach that wasn't working which is to use the .properties file and to implement the DataSourceProvider interface.  This did NOT work for me when I tried it 4 months ago so either the documentation was improved or I got smarter.  This seems to be the easiest way to take existing JNDI based code and make it work in Liferay 7.3.  The basic approach is laid out here: https://help.liferay.com/hc/en-us/articles/360032978871-Connecting-the-Data-Source-Using-a-DataSourceProvider my only concern is that the JNDI approach I used in the past allowed for connection pooling and auto reconnect.  I haven't figured out all the JDBC connection string parameters that need to be passed to make that happen with with the DataSource provider approach yet.
So here is the most direct approach that I took:
 I have existing SB modules that used JNDI in 7.0 of LR.  I started with new SB modules in LR Studio targeting LR 7.3.  I copied the service.xml text from the namespace entry to the closing bracket from the 7.0 version and pasted it into the 7.3 version, replacing the default code that was generated.  IOW I made sure that the DTD for 7.3 and the "service-builder dependency-injector="ds" package-path= " portion was retained. I made sure that I added the 4 lines mentioned in the Help Center article to the portal-ext.properties file:
jdbc.ext.driverClassName=
jdbc.ext.password=
jdbc.ext.url=
jdbc.ext.username=
If you are like me and have multiple external data sources to connect to, then come up with some naming convention to make it easy to know which data source maps to which SB module.I ran the service builder to generate the java source and then I added a package and class for the DataSourceProvider itself. I added it to the SB service module code rather than the API code because as it says in the Help Center article: " the DataSourceProvider must be visible to your *-service module class loader".I added a "services" folder to the META-INF folder in src/main/resources and added the com.liferay.portal.kernel.dao.jdbc.DataSourceProvider file.  That file has one entry that is the package/class name of of the DataSourceProvider you created in the service module.And that was it.  
So the TL;DR; is
1) add the JDBC entries to portal-ext.properties
2)  add the DataSourceProviderImpl class to the service module and then
3) register that class using the /META-INF/services/com.liferay.portal.kernel.dao.jdbc.DataSourceProvider file in your service module.
I am not sure what was getting in the way of me doing this 4 months ago.  I could have been that I was copying the service.xml file over from the 7.0 version and with the DTD and other stuff.  Not sure.  But it is ALL working at the moment when deployed to 7.3CE ga1.  I plan to try it with 7.3.1 CE ga2 sometime today if I get the chance.This has been a LONG road and would have been shorter with some tooling that would have generated the DataSourceProvider or JNDI LR Classloader code automatically as part of the SB generated code.  But this does work and I am thankful for that.
thumbnail
Pete Helgren, modified 3 Years ago.

RE: JNDI to external database - one more time (Answer)

Regular Member Posts: 225 Join Date: 4/7/11 Recent Posts
SOLVED this the correct way:  See this post I just updated:  https://liferay.dev/forums/-/message_boards/message/119963051