
Extend Tables in Another Database
#
This article is based on Liferay 5.0.1 and Tomcat 6 bundle. Ext environment is 5.0.1, except all build.xml files are from trunk (5.0.2), as there were some minor failures with build files in 5.0.1. There are some differences between old and this new version of Liferay and Ext environment so I wanted to have the procedure written. Considering that sometimes community is not very fast and elaborative on answers this article might save you some time.
Preparation #
These installs can be anywere therefore I use aliases to keep it relative.
- Liferay–Tomcat bundle is installed in folder e.g. liferay-portal-tomcat-6.0-5.0.1. which I will refer to as CATALINA_HOME.
- Ext environment is installed in folder e.g. liferay-portal-ext-5.0.1 which I will refer to as EXT_ENV.
- Liferay source 5.0.1 is located in e.g. liferay-portal-src-5.0.1 which I will refer to as LIFERAY_SOURCE.
Few other notes:
- You need to be sure that app.server.Administrator.properties exists in EXT_ENV and that app.server.parent.dir points to CATALINA_HOME. Note: use slashes instead of backslashes.
- If EXT_ENV is in directory with spaces and you execute ant from a dos box, use tilda () notation to avoid spaces, otherwise build will fail at certain phases. E.g. c:\program files\Ext should be c:\progra1\ext when working from dos box. Use /x parameter in dir if needed to find out short name. If using eclipse to execute ant, then this might not be relevant.
Example of app.server.Administrator.properties:
app.server.type=tomcat app.server.parent.dir=C:/liferay-portal-tomcat-6.0-5.0.1 app.server.tomcat.version=6.0 app.server.tomcat.dir=${app.server.parent.dir} app.server.tomcat.classes.global.dir=${app.server.tomcat.dir}/lib app.server.tomcat.lib.endorsed.dir=${app.server.tomcat.dir}/lib/ext app.server.tomcat.lib.global.dir=${app.server.tomcat.dir}/lib/ext app.server.tomcat.lib.support.dir=${app.server.tomcat.dir}/lib/ext app.server.tomcat.support.dir=${app.server.tomcat.dir}/lib/ext app.server.tomcat.zip.name=liferay-portal-tomcat-6.0-${downloads.version.file.name}.zip
ROOT.XML #
Now, we can proceed to the real stuff. So we need to add the another jdbc resource to tomcat. We add that in CATALINA_HOME/conf/Catalina/localhost/ROOT.xml another Resource.
<!-- MySQL --> <Resource name="jdbc/LiferayPool" auth="Container" type="javax.sql.DataSource" driverClassName="com.mysql.jdbc.Driver" url="jdbc:mysql://localhost/lportal?useUnicode=true&characterEncoding=UTF-8" username="root" password="" maxActive="20" /> <Resource name="jdbc/TrainingPool" auth="Container" type="javax.sql.DataSource" driverClassName="com.mysql.jdbc.Driver" url="jdbc:mysql://localhost/training?useUnicode=true&characterEncoding=UTF-8" username="root" password="" maxActive="20" /> <!-- Oracle -->
We are done with tomcat and datasource here. Now we can move to the Ext environment.
service.xml #
Service xml is located at EXT_ENV/ext-impl and let’s append Training entity at the end before </service-builder>.
<entity name="Training" local-service="true" data-source="trainingDataSource" session-factory="trainingSessionFactory" tx-manager="trainingTransactionManager"> <!-- PK fields --> <column name="userId" type="long" primary="true" /> <!-- Other fields --> <column name="dogName" type="String" /> <column name="wifeName" type="String" /> </entity> </service-builder>
As you can see, we have 3 new attributes that we don’t use when we work with lportal database, and they are data-source, session-factory and tx-manager. You can assign these attributes to bean names of your choice, but I would like to follow Liferay naming pattern as above training + TransactionManater, training + SessionFactory, etc.
Now, we need to declare beans that we used in 3 attributes mentioned above . So we meet soon another important config file that we wouldn't have to create if we weren't intend to use a different database.
ext-data-source-spring.xml #
ext-data-source-spring.xml will be created inside EXT_ENV/ext-impl/src/META-INF folder. Actually you can name this file any way you like, but this naming this follows Liferay's pattern.
To keep the life easy, I copied original file from LIFERAY_SOURCE\portal-impl\src\META-INF\data-source-spring.xml and have saved it into EXT_ENV/ext-impl/src/META-INF/ext-data-source-spring.xml
Short explanation of changes - here's original bean:
<bean id="liferayDataSourceTarget" class="com.liferay.util.spring.jndi.PortalDataSourceFactoryBean" lazy-init="true" />
We are not using class PortalDataSourceFactoryBean as it directly points to the liferay datasource. We will use spring’s JndiObjectFactoryBean which takes one argument (property), jndiName, and that’s exactly what we need. Our replacement code is:
<bean id="trainingDataSourceTarget" class="com.liferay.portal.spring.jndi.JndiObjectFactoryBean" lazy-init="true"> <property name="jndiName"> <value>jdbc/TrainingPool</value> </property> </bean>
Now, 3 other beans were changed, but these were simple changes, just renamed IDs from liferayDataSource, liferaySessionFactory, liferayTransactionManager to trainingDataSource, trainingSessionFactory, trainingTransactionManager. Note that I changed arguments (properties) to our new bean IDs. So the final code is:
<?xml version="1.0"?> <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd"> <!-- Copy from: liferay-portal-src-5.0.1\portal-impl\src\META-INF\data-source-spring.xml, paste here and then modify. --> manager="trainingTransactionManager"> --> <beans> <bean id="trainingDataSourceTarget" class="com.liferay.portal.spring.jndi.JndiObjectFactoryBean" lazy-init="true"> <property name="jndiName"> <value>jdbc/TrainingPool</value> </property> </bean> <bean id="trainingDataSource" class="org.springframework.jdbc.datasource.LazyConnectionDataSourceProxy" lazy-init="true"> <property name="targetDataSource"> <ref bean="trainingDataSourceTarget" /> </property> </bean> <bean id="trainingHibernateSessionFactory" class="com.liferay.portal.spring.hibernate.PortalHibernateConfiguration" lazy-init="true"> <property name="dataSource"> <ref bean="trainingDataSource" /> </property> </bean> <bean id="trainingSessionFactory" class="com.liferay.portal.dao.orm.hibernate.SessionFactoryImpl" lazy-init="true"> <property name="sessionFactoryImplementor"> <ref bean="trainingHibernateSessionFactory" /> </property> </bean> <bean id="trainingTransactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager" lazy-init="true"> <property name="dataSource"> <ref bean="trainingDataSource" /> </property> <property name="sessionFactory"> <ref bean="trainingHibernateSessionFactory" /> </property> </bean> </beans>
I hope you remember that jdbc/TrainingPool comes from ROOT.XML. Perhaps you wonder now, how Liferay will know about our newly created ext-data-source-spring.xml file and all beans we did there? Well, it wont - we have to tell Liferay to take our new xml into account.
Hopefully in some future version this ext-data-source-spring.xml might be already included in liferay implementation (vote LEP-6570) and then we might skip the following section, but for now, we have to do changes in portal-ext.properties and add our ext-data-source-spring.xml there, so let's see how.
portal-ext.properties #
This file is located at EXT_ENV/ext-impl/src (and later, after first build-service, at EXT_ENV/ext-impl/classes - always check that they are synchronized, unless you always do annoying ant clean) and we will again do some copy-pasting. So let’s open original file to get some inspiration: LIFERAY_SRC/portal-impl/classes/portal.properties and copy whole spring.configs section:
spring.configs=\ META-INF/activemq-spring-jms.xml,\ META-INF/data-source-spring.xml,\ META-INF/misc-spring.xml,\ META-INF/counter-spring.xml,\ META-INF/documentlibrary-spring.xml,\ META-INF/lock-spring.xml,\ META-INF/mail-spring.xml,\ META-INF/mail-spring-jms.xml,\ META-INF/portal-spring.xml,\ META-INF/portal-spring-jcr.xml,\ META-INF/portal-spring-jms.xml,\ META-INF/ext-spring.xml
Now at the end add one more line, you guess know which one: META-INF/ext-data-source-spring.xml so our final EXT_ENV/ext-impl/src/portal-ext.properties looks like:
## ## You can override portal.properties by specifying your own settings in this ## file. ## spring.configs=\ META-INF/activemq-spring-jms.xml,\ META-INF/data-source-spring.xml,\ META-INF/misc-spring.xml,\ META-INF/counter-spring.xml,\ META-INF/documentlibrary-spring.xml,\ META-INF/lock-spring.xml,\ META-INF/mail-spring.xml,\ META-INF/mail-spring-jms.xml,\ META-INF/portal-spring.xml,\ META-INF/portal-spring-jcr.xml,\ META-INF/portal-spring-jms.xml,\ META-INF/ext-spring.xml,\ META-INF/ext-data-source-spring.xml
It’s over with configuration. Lets build and see what happens.
Let's build #
Lets do from dos box in EXT_ENV/ext-impl folder: Ant build-service
No errors should appear. After generation is done, let’s do quick and dirty tirck to save some time. By now we should have two classes:
EXT_ENV\ext-impl\src\com\blah\blah\blah\blah\service\impl\TrainingLocalServiceImpl.java EXT_ENV\ext-service\src\com\blah\blah\blah\blah\service\persistence\TrainingUtil.java
Open both if your favorite editor (be carefull don’t change TrainingUtil.java by accident!):
- In your TrainingLocalServiceImpl class add import to TrainingUtil, e.g. import com.blah.blah.blah.service.persistence.TrainingUtil;
- copy public functions from TraningUtil, starting from the first one (including that one) to the countAll() (including that one as well).
- paste them inside the TrainingLocalServiceImpl class
- Delete all static keywords (Find/replace static to [space])
- Find/Replace or rename all getPersistence() to TrainingUtil
That’s all. However this is really a bad way to get TrainingLocalServiceImpl class implemented ! You need to add business logic to TrainingLocalServiceImpl. For the sake of simplicity of this article I have above trick to make it shorter.
Now rerun ant to promote our new public interface to other classes:
Ant build-service Ant compile Ant jar Ant deploy
This is end of java programing, if we might call it that way...
Note that after build-service that you did here again, few new classes were generated. One of them will be important for use later, and that's not located in persistance package (like TrainingUtil was that we mentioned above)!
EXT_ENV\ext-service\src\com\blah\blah\blah\blah\service\TrainingServiceUtil.java.
Create database and table #
Last task is to create table in the training database, and create database as well. Use your favorite tool to create database. DDL to create table is in EXT_ENV/sql/portal-tables.sql, open it and search for Training. If it's not there, just type "ant" from dos box in EXT_ENV/sql and it will get generated.
create table Training ( userId LONG not null primary key, dogName VARCHAR(75) null, wifeName VARCHAR(75) null );
If using MySql you might want to modify it like this:
CREATE TABLE `training` ( `userId` bigint(20) NOT NULL DEFAULT '0', `dogName` varchar(75) DEFAULT NULL, `wifeName` varchar(75) DEFAULT NULL, PRIMARY KEY (`userId`) ) ENGINE=MyISAM DEFAULT CHARSET=latin1;
You can add two lines to table manually:
insert into training.training values (1,"tom","anna"); insert into training.training values (2,"boxy","silvy");
Testing from existing portlet #
Finally add this two lines of code in any of your backing bean init function (if using JSF), to test if it’s working:
Note you need:
- appropriate "import ... service.TrainingLocalServiceUtil;" in your existing backing bean. (from ext-service)
- ext-impl.jar in your WEB-INF/lib folder of your portlet.
Warning: be sure not to "import ...service.persistence.TrainingUtil;" (also from ext-service), as calling findAll() from this class/package will cause binding exceptions.
List a=TrainingLocalServiceUtil.findAll(); System.out.println("--> Size of Training table in training database: "+a.size());
And console output should be:
--> Size of Training table in training database: 2
Now. It’s really over.
Troubleshooting #
However, few hints. As the final result of above procedure you will have 2 jar files generated:
EXT_ENV/ext-impl/ext-impl.jar EXT_ENV/ext-service/ext-service.jar
During build:
- ext-impl.jar should coitain ext-data-source-spring.xml in META-INF directory, as well as other 3 ext- xmls (hbm, model-hints and spring). If not check your build.xml files and take new ones from the trunk (5.0.2 currently).
During deploy:
- ext-service.jar will be copied to the CATALINA_HOME/lib/ext. Check that it’s there.
- ext-impl.jar will be copied to the CATALINA_HOME/ webapps\ROOT\WEB-INF\lib. Check that it’s there
- CATALINA_HOME/webapps/ROOT/WEB-INF/classes should contain 2 property files: portal-ext.properties and system-ext-roperties, where portal-ext.properties should have content as we wrote above. If not copy it manually and check your build files and take new ones from the trunk (5.0.2 currently).
When using this service in your portlet:
- ext-impl.jar should also be included in your portlet lib directory.
See Also #
You could achieve similar goals by developing with Expando