Lucas
Lucas

Reputation: 14949

Liferay service builder JNDI lookup uses the wrong naming context

I have a servicebuilder portlet that is configured via JNDI:

<Context antiJARLocking="true" useHttpOnly="true">
  <ResourceLink name="app/url" global="my-app/app/url"
    type="java.lang.String" />
</Context>

In my ext-spring.xml, I try to pull that value using:

<jee:jndi-lookup jndi-name="java:comp/env/app/url" />

However, the InitialContextthat is used to look this value up, seems to be the InitialContext of the ROOT webapp (liferay itself) as the only thing it contains is the java:comp/env/jdbc/LiferayPool database connection pool. I know JNDI contexts are tied to the the ClassLoader determined by:

Thread.currentThread().getContextClassLoader();

And I know liferay mucks with the classloader before running the portlet initialization. But I cannot, for the life of me, figure out how to get around this. Any suggestions?

Upvotes: 0

Views: 1283

Answers (1)

astrohome
astrohome

Reputation: 475

I have a working solution for you but it is a little bit different of what you've described in your post.

First of all, I propose you to move the declaration of JNDI to server.xml of Tomcat, specifically to < GlobalNamingResources > tag.

Here is an example:

<GlobalNamingResources>
<!-- Editable user database that can also be used by
     UserDatabaseRealm to authenticate users
-->
<Resource
   name="jdbc/CustomDBPoolShared"
   auth="Container"
   type="javax.sql.DataSource"
   factory="org.apache.tomcat.jdbc.pool.DataSourceFactory"
   driverClassName="oracle.jdbc.OracleDriver"
   url="jdbc:oracle:thin:@YOUR_SERVER:1521:YOUR_SERVICE"
   username="USERNAME"
   password="PASSWORD"
   maxActive="20"
   maxIdle="5"
   maxWait="10000"
/>
......
</GlobalNamingResources>

After that you should make a ResourceLink in context.xml:

<Context>

<!-- Default set of monitored resources -->
<WatchedResource>WEB-INF/web.xml</WatchedResource>

<!-- Uncomment this to disable session persistence across Tomcat restarts -->
<!--
<Manager pathname="" />
-->

<!-- Uncomment this to enable Comet connection tacking (provides events
     on session expiration as well as webapp lifecycle) -->
<!--
<Valve className="org.apache.catalina.valves.CometConnectionManagerValve" />
-->
<ResourceLink name="jdbc/CustomDBPool" global="jdbc/CustomDBPoolShared" type="javax.sql.DataSource"/>
....
</Context>

This will help you to deal with concurrent access if ever you'll have two portlets accessing the same database.

And, finally, ext-spring.xml:

   <!-- Custom Beans -->
<bean id="digitalHibernateSessionFactory" class="com.liferay.portal.spring.hibernate.PortletHibernateConfiguration" lazy-init="true">
    <property name="dataSource">
        <ref bean="customDBDataSource"/>
    </property>
</bean>
<bean id="customDBSessionFactory" class="com.liferay.portal.dao.orm.hibernate.SessionFactoryImpl" lazy-init="true">
    <property name="sessionFactoryImplementor">
        <ref bean="digitalHibernateSessionFactory" />
    </property>
</bean>
<bean id="customDBDataSourceTarget" class="com.liferay.portal.spring.jndi.JndiObjectFactoryBean" lazy-init="true">
    <property name="jndiName">
        <value>jdbc/customDBPool</value>
    </property>
</bean>
<bean id="customDBDataSource" class="org.springframework.jdbc.datasource.LazyConnectionDataSourceProxy" lazy-init="true">
    <property name="targetDataSource">
        <ref bean="customDBDataSourceTarget" />
    </property>
</bean>

Notice the jdbc/customDBPool

Please note the jdbc/customDBPool which is described in context.xml and which is available not only for ROOT classloader.

In the end you should reference the proper dataSource in your service.xml of LiferayServiceBuilder like that:

<entity name="MyCustomEntity" table="CUSTOM_ENTITY" local-service="true" remote-service="false" data-source="customDBDataSource" session-factory="customDBSessionFactory" cache-enabled="false">
.....
</entity>

Upvotes: 1

Related Questions