« Back to Development

Extend Liferay Tables

Tags: development

Introduction#

In the following sections you will be taken step by step from nothing to a fully working user add that takes in your new field(s).

One of the most common needs of users of Liferay is to extend certain functionality for their needs. This article will cover extending the user table to accept their hometown newspaper. Clearly this is just an example, and could be changed to include the name of their spouse, dog, cat, or whatever is wanted.

Overview

The idea here is to create a new table which has your custom field(s) for the user table. If you are adding hometown newspaper for example, you would create a new table in the Liferay database that has a unique id column, your hometown newspaper column, and a reference to the user that is associated with this data.

You can do this by running our service builder to create the extended user class, and then in the LocalServiceImpl file you would add the data you needed to the extra table.

Details#

In the following sections you will be taken step by step from nothing to a fully working user add that takes in your new field(s).

Service Builder#

Step one is to create a service.xml file with your new table information in in. You will save this file as /ext/ext-impl/service.xml.

This is the file we used for the hometown newspaper addition:

 <?xml version="1.0"?>
 <!DOCTYPE service-builder PUBLIC "-//Liferay//DTD Service Builder 5.1.0//EN" 
    "http://www.liferay.com/dtd/liferay-service-builder_5_1_0.dtd">
 <service-builder root-dir=".." package-path="com.ext.portlet">
    <portlet name="AddUserEXT" short-name="AddUserEXT" /> 
    
    <!-- This is the folder that will be created under ext-impl -->
    
    <entity name="AddUserExtras" local-service="true">
        <!-- This is the table that will be used, and the class that will be generated -->
        <!-- PK fields -->
        <column name="entryId" type="String" primary="true" />
        <!-- Other fields -->
        <column name="HomeTownNewspaper" type="String" />
    </entity>
 </service-builder>

You now need to run "ant build-service" from the "ext/ext-impl/" folder.

More information about service.xml creation here

AddUserExtrasLocalServiceImpl #

Now that the service builder has run, the following file should have been created (along with others): ext/ext-impl/src/com/ext/portlet/AddUserEXT/service/impl/AddUserExtrasLocalServiceImpl.java

Go ahead and open that file. We now need to add the addUser function to our class. After your edition, it should look something like this (depending on the liferay version you are using):

  
  import com.ext.portlet.AddUserEXT.service.AddUserExtrasLocalService;
  import com.ext.portlet.AddUserEXT.model.AddUserExtras;
  //^ the AddUserExtras is not available in programming time. Only the ./impl/AddUserExtras.java is.
  //  however you should use the interface file AddUserExtras.
  import com.ext.portlet.AddUserEXT.service.persistence.AddUserExtrasUtil;
  
  
  [...]
  
  
  import com.liferay.portal.model.User;
  import com.liferay.portal.service.UserServiceUtil;
  import com.liferay.portal.PortalException;
  import com.liferay.portal.SystemException;
  import java.util.Locale;
  
  
  [...]
  
  public AddUserExtras addUser(
    java.lang.String companyId, boolean autoUserId,
    java.lang.String userId, boolean autoPassword,
    java.lang.String password1, java.lang.String password2,
    boolean passwordReset, java.lang.String emailAddress,
    java.util.Locale locale, java.lang.String firstName,
    java.lang.String middleName, java.lang.String lastName,
    java.lang.String nickName, java.lang.String prefixId,
    java.lang.String suffixId, boolean male, int birthdayMonth,
    int birthdayDay, int birthdayYear, java.lang.String jobTitle,
    java.lang.String organizationId, java.lang.String locationId, 
    java.lang.String dogName)
    throws 
      PortalException, 
      SystemException
    {
        // Note this call is dependent upon your version of liferay.  Ensure the signature matches the API in
        // portal-impl/src/com/liferay/portal/service/UserLocalServiceUtil.java
      User user = null;
      try{
        user = UserServiceUtil.addUser(
          companyId, autoUserId, userId, autoPassword, password1, password2,
          passwordReset, emailAddress, locale,  firstName, middleName, lastName,
          nickName, prefixId, suffixId, male, birthdayMonth, birthdayDay, birthdayYear,
          jobTitle, organizationId, locationId
        );
      }catch(Exception e){
          throw new PortalException(e.getMessage());
      }
      
        // We now need to add the user to our new table
        // By default liferay uses the e-mail address as a primary key, so I will too.
        AddUserExtras userInfo = AddUserExtrasUtil.create(email);
        userInfo.setHomeTownNewspaper(newspaper);
        return AddUserExtrasUtil.update(userInfo);
   }
 }

Now that we have added addUser to our new class, when our class is called it will add a user to the liferay user table as well as our extra fields to our new table, and they will be linked via the e-mail address.

When making changes in the extension environment, the only files within the "impl" folder that you should touch are the Impl classes. Everything else is generated for you. Now that we've added a method to AddUserExtrasLocalServiceImpl.java, by simply running build-service on your service.xml, it will propagate that method signature to all wrapping classes, including your ServiceUtil.java classes.

Mapping the UI to the Service Layer #

In order to call your newly created addUser() method, we need to map the front-end to call our back-end. This can be done in two ways, depending on your need. One option is to simply override our struts action-mapping. The other option is to simply create your own struts action-mapping.

Overriding our struts action-mapping #

Since we know that within the core source, /my_account/create_account maps to AddUserAction which then calls UserServiceUtil.addUser(), we want to make modifications such that it calls our newly created addUser() method instead. This can be easily done by adding another action-mapping within /ext-web/docroot/WEB-INF/struts-config.xml. Since action-mappings are loaded sequentially, and since the struts-config.xml of the extension environment is loaded AFTER the struts-config.xml of the portal core, we know that any action-mappings in the extension environment that have the same path name will override those action-mappings in the portal core. Hence, we can then do something like this:

 <action path="/my_account/create_account" type="com.ext.portlet.adduserextras.action.CreateUserAction">
    <forward name="portlet.my_account.create_account" path="portlet.my_account.create_account" />
    <forward name="portlet.my_account.view" path="portlet.my_account.view" />
 </action>

This will effectively override the portal core's struts action-mapping to call our specified action class instead of Liferay's. Moreover, no changes to the JSP are necessary since the form is still going to the same struts action-mapping.

Creating a new struts action-mapping #

Alternatively, we can just create our own new struts action-mapping. This sometimes makes sense when you don't want to remove any of our functionality (i.e. you may need to use our struts action-mapping for something else). Add the following to ext/ext-web/docroot/WEB-INF/struts-config.xml:

 <action path="/AddUserExtras/create_account" type="com.ext.portlet.adduserextras.action.CreateUserAction">
    <forward name="portlet.my_account.create_account" path="portlet.my_account.create_account" />
    <forward name="portlet.my_account.view" path="portlet.my_account.view" />
 </action>

We now need to edit Liferay to call our addUser instead of the default Liferay addUser.

You need to create the following file:

 ext/ext-web/docroot/html/portlet/my_account/create_account.jsp

The easiest way to populate this file is to grab the same file from the portal source, that's in:

 portal/portal-web/docroot/html/portlet/my_account/create_account.jsp

This file contains the form that is used when the new account information is being gathered. You'll want to add your input fields to the form to gather and capture the new fields that your user info addition object needs. The file is pretty simple, just follow the existing example here and you should be fine.

Now that you have the file copied, change the struts to point to our new file instead of the old liferay one, just in the case you have not overwritten the struts-config.xml file!

Change this line:

<form action="<portlet:actionURL>
    <portlet:param name="struts_action"   value="/**my_account/create_account**" />
    </portlet:actionURL>" method="post"  name="<portlet:namespace />fm" 
    onSubmit="<portlet:namespace />createAccount(); return false;">

to this:

<form action="<portlet:actionURL>
    <portlet:param name="struts_action"  value="/**AddUserExtras/create_account**" />
    </portlet:actionURL>" method="post"  name="<portlet:namespace />fm" 
    onSubmit="<portlet:namespace />createAccount(); return false;">

Don't forget to add your new field to this page so people can enter their hometown newspaper!

For this, you can add a new AddUserExtras variable to your new create_account.jsp under User and Contact variables:

 User user2 = null;
 Contact contact2 = null;
 **AddUserExtras userInfo2 = null;**

Then add your hometown newspaper input box wherever you see fit:

 <tr>
 	<td>
 		<%= LanguageUtil.get(pageContext, "HomeTownNewspaper") %>
 	</td>
 	<td style="padding-left: 10px;"></td>
 	<td>
 		<liferay-ui:input-field model="<%= AddUserExtras.class %>" bean="<%= userInfo2 %>" field="HomeTownNewspaper" />
 	</td>
 </tr>

In order to access to the new class AddUserExtras from the JSP file, you will need to edit the file .../hml/common/init-ext.jsp in order to import the model file. The simpliest way to do it is to copy the .../hml/common/init-ext.jsp file from the portal source code to the extention environment and add it the import code. The init-ext.jsp must look like:

<%@ page import="com.ext.portlet.adduserEXT.model.AddUserExtras" %>

Remember that the model file is not available at programming time, only the implementation file is available, because AddUserExtras is an interface to the AdduserExtrasImpl.java.

Creating an actionclass to call your new Impl file #

You are now going to create the following file:

 ext/ext-impl/src/com/ext/portlet/adduserextras/action/CreateUserAction.java

And make it look something like this: (Please make sure to check all of the parameter names to be consistent with the names appear in 'create_account.jsp' ('first_name' may have become 'firstName') since there has been some changes on input names throughout version changes in liferay core codes! In fact, you are encouraged to check 'com.liferay.portlet.myaccount.action.AddUserAction' to see if there are any changes in create_user implementation.)

 package com.ext.portlet.adduserextras.action;
 public class CreateUserAction extends PortletAction {
   public void processAction(ActionMapping mapping, ActionForm form,
       PortletConfig config, ActionRequest req, ActionResponse res)
     throws Exception {
       // declarations
        String firstName = req.getParameter("first_name");
        String lastName = req.getParameter("last_name");
        String address1 = req.getParameter("address1");
        String address2 = req.getParameter("address2");
        String newspaper = req.getParameter("Hometown Newspaper");
        String emailAddress = req.getParameter("email_address");
        String userId = req.getParameter("user_id");
        String password1 = req.getParameter("password_1");
        String password2 = req.getParameter("password_2");
        try {
            AddUserExtrasLocalServiceUtil.addUser(
                firstName, lastName, emailAddress, userId, password1, password2, newspaper);
            req.setAttribute("success", "true");
            setForward(req, "portlet.my_account.view");
        } 
        catch (Exception e) {
           if (e != null &&
             e instanceof CaptchaException ||
             e instanceof DuplicateUserEmailAddressException ||
             e instanceof DuplicateUserIdException ||
             e instanceof ReservedUserEmailAddressException ||
             e instanceof ReservedUserIdException ||
             e instanceof UserEmailAddressException ||
             e instanceof UserFirstNameException ||
             e instanceof UserIdException ||
             e instanceof UserLastNameException ||
                    {
                     SessionErrors.add(req, e.getClass().getName());
                     setForward(req, "portlet.my_account.create_account");
                    }
                      else if (e != null &&
                        e instanceof UserPasswordException) {
                          UserPasswordException upe = (UserPasswordException)e;
                          SessionErrors.add(req, e.getClass().getName(), upe);
                          setForward(req, "portlet.my_account.create_account");
        }
                      else if (e != null &&
                         e instanceof PrincipalException) {
                           SessionErrors.add(req, e.getClass().getName());
                           setForward(req, "portlet.my_account.create_account");
                     }
                      else {
                         req.setAttribute(PageContext.EXCEPTION, e);
                         setForward(req, "portlet.my_account.create_account");
                     }
         }

Database #

Finally, after running build-service on your service.xml, you should note that a corresponding table should exist within portal-tables.sql that matches your entity name. You will need to create this table within your database, as it will hold the new fields you specified. The portal-tables.sql file is a template file - running "ant build-db" within ext/sql/ with create the database specific files within ext/sql/create/ and ext/sql/portal/.

Liferay can create the database for you if none exists. In order to make this work with your new tables you need to copy the the following files from the sql folder to ext-impl/classes/com/liferay/portal/tools/sql/dependencies:

  • portal-data-common.sql
  • portal-data-counter.sql
  • portal-data-release.sql
  • portal-data-sample.vm
  • portal-tables.sql
  • indexes.sql
  • quartz-tables.sql
  • sequences.sql
  • update-.sql

Here is a ANT target you can add to build.xml

	<target name="copy-dependencies"> 
		<copy todir="${ext-impl.classes.dir}/com/liferay/portal/tools/sql/dependencies">
			<fileset
				dir="${project.dir}/sql"
				includes="portal-data-common.sql,portal-data-counter.sql,portal-data-release.sql,portal-data-sample.vm,portal-tables.sql,indexes.sql,quartz-tables.sql,sequences.sql,update-*.sql"
			/>
		</copy>
	</target>
	

Conclusion#

Rather than assuming that you are replacing Liferay's classes, the better way to think of it is that you are extending Liferay's classes.

0 Attachments
57457 Views
Average (2 Votes)
The average rating is 4.5 stars out of 5.
Comments
Threaded Replies Author Date
Can you attach a full example? Thx. v n September 16, 2008 7:02 AM
Has anyone got this to work with 5.1.2 tomcat... Steve Koft November 3, 2008 10:25 PM
NullPointerException I try to test this... Grouhan Olivier November 14, 2008 9:13 AM
ServiceBuilder don't work in 5.1.2 version See... Grouhan Olivier December 2, 2008 5:36 AM
In this page: ... Jonathan Doe Bridge April 9, 2009 3:41 PM
Hi there, I have similarly extended Region... Dmitri Vass November 16, 2008 6:04 PM
Thank you. It's very useful wiki. I tested this... relax sun March 26, 2009 10:01 AM
You tested this with Liferay Portal 5.2.0 and... Jonathan Doe Bridge April 9, 2009 3:39 PM
Im getting following exception while running... Satish Bejgum March 30, 2009 5:14 AM
Hello Satish, This is the service.xml which it... Ruben Aguilera May 27, 2009 4:30 AM
Hi I followed the steps mentioned above... Takshila Vidushi Gautam April 13, 2009 4:52 AM
Hello, when I try to run "ant build-service", I... Will T June 8, 2009 1:43 AM
I also receive the same error on liferay 5.2.2.... Flavel --------- July 7, 2009 9:09 AM
Hello everyone, I am new in using Liferay5.2.1... Panagiotis Andriopoulos October 8, 2009 7:07 AM
FYI, for those that are looking for it, in LR... Kevin Britton October 4, 2010 3:11 AM
Hello, Anyone know if Can I do the same... Andrés Cerezo February 22, 2011 1:59 AM
is this wiki works for liferay 6.1.1 ? anyones ? Arif Rahman October 8, 2012 11:27 PM

Can you attach a full example? Thx.
Posted on 9/16/08 7:02 AM.
Has anyone got this to work with 5.1.2 tomcat 6.x?

i can only get part of the expected output and what is generated has lots of problems.
Posted on 11/3/08 10:25 PM.
NullPointerException

I try to test this tutorial but i have got an error at step one when i run ant build.
This is java stack trace :

java.lang.NullPointerException
at java.io.Reader.<init>(Reader.java:61)
at java.io.InputStreamReader.<init>(InputStreamReader.java:55)
at com.liferay.portal.kernel.util.StringUtil.read(StringUtil.java:530)
at com.liferay.portal.kernel.util.StringUtil.read(StringUtil.java:519)
at com.liferay.portal.kernel.util.StringUtil.read(StringUtil.java:488)
at com.liferay.portal.tools.servicebuilder.ServiceBuilder.getEntity(ServiceBuilder.­java:1094)
at com.liferay.portal.tools.servicebuilder.ServiceBuilder._createRemotingXml(Servic­eBuilder.java:2124)
at com.liferay.portal.tools.servicebuilder.ServiceBuilder.<init>(ServiceBuilder.jav­a:971)
at com.liferay.portal.tools.servicebuilder.ServiceBuilder.<init>(ServiceBuilder.jav­a:390)
at com.liferay.portal.tools.servicebuilder.ServiceBuilder.main(ServiceBuilder.java:­157)

Has anyone an idea ?
Posted on 11/14/08 9:13 AM.
Hi there,
I have similarly extended Region class (needed to have sub regions, so that I can go recursively to many lavels of sub regions). It works well, but... remote-service of RegionExt JSON won't compile to have a function that returns List<Regions> ;-(
It will only compile to give back RegionExt specific stuff...

Can anyone help me please
Posted on 11/16/08 6:04 PM.
ServiceBuilder don't work in 5.1.2 version

See the bug http://issues.liferay.com/browse/LEP-7492
Posted on 12/2/08 5:36 AM in reply to Grouhan Olivier.
Thank you. It's very useful wiki. I tested this with LP 5.2.0 and works very well.
Posted on 3/26/09 10:01 AM.
Im getting following exception while running the build-service:
com.liferay.portal.kernel.xml.DocumentException: Error on line 3 of document file:///F:/Satishb/ext/ext-impl/service.xml : Attribute "root-dir" must be declared for element type "service-builder". Nested exception: Attribute "root-dir" must be declared for element type "service-builder".
at com.liferay.portal.xml.SAXReaderImpl.read(SAXReaderImpl.java:344)
at com.liferay.portal.kernel.xml.SAXReaderUtil.read(SAXReaderUtil.java:126)
at com.liferay.portal.tools.servicebuilder.ServiceBuilder.<init>(ServiceBuilder.jav­a:490)
at com.liferay.portal.tools.servicebuilder.ServiceBuilder.<init>(ServiceBuilder.jav­a:390)
at com.liferay.portal.tools.servicebuilder.ServiceBuilder.main(ServiceBuilder.java:­157)

what should i do?
Posted on 3/30/09 5:14 AM.
You tested this with Liferay Portal 5.2.0 and worked??

Please can you explain me how?

I mess making the service.xml file. How you configured the service.xml to run it on liferay 5.2 version?

Thanks!!!!!!
Posted on 4/9/09 3:39 PM in reply to Giuseppe Cinque.
In this page:

http://www.liferay.com/web/guest/community/wiki/-/wiki/Main/Service%20Builder

Exp­lain that error.

May be you have the same problem as me, trying to make a proper service.xml TT_TT
Posted on 4/9/09 3:41 PM in reply to Grouhan Olivier.
Hi
I followed the steps mentioned above ,still I am getting following exception ,when I ran ext's build service. I am using 5.2.2 version.Can anybody help?


java.lang.NullPointerException
at java.io.Reader.<init>(Unknown Source)
at java.io.InputStreamReader.<init>(Unknown Source)
at com.liferay.portal.kernel.util.StringUtil.read(StringUtil.java:570)
at com.liferay.portal.kernel.util.StringUtil.read(StringUtil.java:559)
at com.liferay.portal.kernel.util.StringUtil.read(StringUtil.java:528)
at com.liferay.portal.tools.servicebuilder.ServiceBuilder.getEntity(ServiceBuilder.­java:1118)
at com.liferay.portal.tools.servicebuilder.ServiceBuilder._createRemotingXml(Servic­eBuilder.java:2206)
at com.liferay.portal.tools.servicebuilder.ServiceBuilder.<init>(ServiceBuilder.jav­a:994)
at com.liferay.portal.tools.servicebuilder.ServiceBuilder.<init>(ServiceBuilder.jav­a:393)
at com.liferay.portal.tools.servicebuilder.ServiceBuilder.main(ServiceBuilder.java:­159)
compile:
Compiling 19 source files to C:\projects\liferay\ext\ext-service\classes
BUILD SUCCESSFUL
Posted on 4/13/09 4:52 AM.
Hello Satish,

This is the service.xml which it works in Liferay 5.2.3:

<?xml version="1.0"?>
<!DOCTYPE service-builder PUBLIC "-//Liferay//DTD Service Builder 5.1.0//EN"
"[http://www.liferay.com/dtd/liferay-service-builder_5_1_0.dtd]">
<service-builder package-path="com.ext.portlet">
<namespace>UserEXT</namespace>

<!-- This is the folder that will be created under ext-impl -->

<entity name="AddUserExtras" local-service="true">
<!-- This is the table that will be used, and the class that will be generated -->
<!-- PK fields -->
<column name="entryId" type="String" primary="true" />
<!-- Other fields -->
<column name="HomeTownNewspaper" type="String" />
</entity>
</service-builder>
Posted on 5/27/09 4:30 AM in reply to satish bejgum.
Hello, when I try to run "ant build-service", I have this error :
java.lang.NullPointerException
at java.io.Reader.<init>(Reader.java:61)
at java.io.InputStreamReader.<init>(InputStreamReader.java:55)
at com.liferay.portal.kernel.util.StringUtil.read(StringUtil.java:530)
at com.liferay.portal.kernel.util.StringUtil.read(StringUtil.java:519)
at com.liferay.portal.kernel.util.StringUtil.read(StringUtil.java:488)
at com.liferay.portal.tools.servicebuilder.ServiceBuilder.getEntity(ServiceBuilder.­java:1094)
at com.liferay.portal.tools.servicebuilder.ServiceBuilder._createRemotingXml(Servic­eBuilder.java:2124)
at com.liferay.portal.tools.servicebuilder.ServiceBuilder.<init>(ServiceBuilder.jav­a:971)
at com.liferay.portal.tools.servicebuilder.ServiceBuilder.<init>(ServiceBuilder.jav­a:390)
at com.liferay.portal.tools.servicebuilder.ServiceBuilder.main(ServiceBuilder.java:­157)
compile:
Compiling 11 source files to C:\liferay-ext\ext-service\classes
C:\liferay-ext\ext-service\src\com\ext\portlet\service\persistence\AddFileExtras­Persistence.java:7: cannot find symbol
symbol : class NoSuchAddFileExtrasException
location: package com.ext.portlet
throws com.ext.portlet.NoSuchAddFileExtrasException,
^
C:\liferay-ext\ext-service\src\com\ext\portlet\service\persistence\AddFileExtras­Persistence.java:44: cannot find symbol
symbol : class NoSuchAddFileExtrasException
location: package com.ext.portlet
throws com.ext.portlet.NoSuchAddFileExtrasException,
^
C:\liferay-ext\ext-service\src\com\ext\portlet\service\persistence\AddFileExtras­Util.java:13: cannot find symbol
symbol : class NoSuchAddFileExtrasException
location: package com.ext.portlet
throws com.ext.portlet.NoSuchAddFileExtrasException,
^
C:\liferay-ext\ext-service\src\com\ext\portlet\service\persistence\AddFileExtras­Util.java:60: cannot find symbol
symbol : class NoSuchAddFileExtrasException
location: package com.ext.portlet
throws com.ext.portlet.NoSuchAddFileExtrasException,
^
4 errors

BUILD FAILED
C:\liferay-ext\ext-impl\build-parent.xml:306: The following error occurred while executing this line:
C:\liferay-ext\ext-service\build-parent.xml:28: Compile failed; see the compiler error output for details.

How can I resolve this? I use liferay 5.1.2, is there any problems with this version of liferay?
Posted on 6/8/09 1:43 AM.
I also receive the same error on liferay 5.2.2. It generated some files. I am unsure if these are all the files I will be needing. I also tried running a service.xml that seemed to come with liferay (reports). This also failed with the same error. It seems like it could be related to the version or my machine and not the code.
Posted on 7/7/09 9:09 AM in reply to Willy Toscer.
Hello everyone,

I am new in using Liferay5.2.1 and I want to add a new table in Liferay database...I follow the steps above (create a service.xml into ext\ext-impl\.., add a new target on the ext/ext-impl/build.xml, execute "ant build-service-portlet-extras") as you recommend and the built was successful, without errors.

The main problem is that after built is not created any other file in my directory (like AddUserExtrasLocalServiceImpl.java in http://www.liferay.com/web/guest/community/wiki/-/wiki/Main/Extend%20Liferay%20T­ables example).

Can anybody help me???

Thanks in advanced.
Posted on 10/8/09 7:07 AM in reply to Flavel ---------.
FYI, for those that are looking for it, in LR 6.0, the create_account.jsp file is under "login", not "my_account"
Posted on 10/4/10 3:11 AM in reply to Panagiotis Andriopoulos.
Hello, Anyone know if Can I do the same tutorial in the 6.0.5 release?

Thanks.
Posted on 2/22/11 1:59 AM in reply to Kevin Britton.
is this wiki works for liferay 6.1.1 ? anyones ?
Posted on 10/8/12 11:27 PM.