Setup Rspec, factory bot and database cleaner for Rails 5.2.6

Let’s find out how to configure a best test suite in Rails by using Rspec framwork and the other libraries that support rspec for writing smooth tests. We will be removing rails native test folder and other configurations.

For that we will be using Rspec, Factory bot for factories, Database cleaner.

Lets start from adding these gems into our Gemfile

group :development, :test do
  # Rspec testing module and needed libs
  gem 'factory_bot_rails', '5.2.0'
  gem 'rspec-rails', '~> 4.0.0'

group :test do
  # db cleaner for test suite 
  gem 'database_cleaner-active_record', '~> 2.0.1'

Now do

bunde install # this installs all the above gems

If you have already built the Rails app, your app may contain Rails own test suite. Remove the native rails test suite to add Rspec module to your project.

We use Rspec over rails native test module because rspec provides better helpers and machnism than Rails native test.

in application.rb file comment the following line

# require 'rails/test_unit/railtie'

inside the class Application add this line:

# Don't generate system test files.
config.generators.system_tests = nil

Remove the native rails test folder:

rm -r test/

We use factories over fixtures. Remove this line from rails_helper.rb

config.fixture_path = "#{::Rails.root}/spec/fixtures"

and modify this line to:

config.use_transactional_fixtures = false # instead of true

This is for preventing rails to generate the native test files when we run rails generators.

Database Cleaner

Now we configure the database cleaner that is used for managing data in our test cycles.

Open rails_helper.rb file and require that module

require 'rspec/rails'
require 'database_cleaner'  # <= add here

Note: Use only if you run integration tests with capybara or dealing with javascript codes in the test suite.

“Capybara spins up an instance of our Rails app that can’t see our test data transaction so even tho we’ve created a user in our tests, signing in will fail because to the Capybara run instance of our app, there are no users.”

I experienced database credentials issues:

➜ rspec
An error occurred while loading ./spec/models/user_spec.rb.
Failure/Error: ActiveRecord::Migration.maintain_test_schema!

  Access denied for user 'username'@'localhost' (using password: NO)

First I thought of using database cleaner, later I realised that this error is because of my credentials.yml.enc file was corrupted somehow. I don’t know how that happend. So try to edit and see your credentials still exists in this file.

EDITOR="code --wait" bin/rails credentials:edit

Now in the Rspec configuration block we do the Database Cleaner configuration.

Add the following file:


Inside, add the following:

# DB cleaner using database cleaner library
RSpec.configure do |config|
  # This says that before the entire test suite runs, clear 
  # the test database out completely
  config.before(:suite) do
    DatabaseCleaner.strategy = :transaction

  # This sets the default database cleaning strategy to 
  # be transactions
  config.before(:each) do
    DatabaseCleaner.strategy = :transaction

  # include this if you uses capybara integration tests
  config.before(:each, :js => true) do
    DatabaseCleaner.strategy = :truncation

  # These lines hook up database_cleaner around the beginning 
  # and end of each test, telling it to execute whatever 
  # cleanup strategy we selected
  config.before(:each) do

  config.after(:each) do

and be sure to require this file in rails_helper.rb

require 'rspec/rails'
require 'database_cleaner'
require_relative 'support/database_cleaner'  # <= here

Configure Factories

Note: We use factories over fixtures because factories provide better features that make writing test cases an easy task.

Create a folder to generate the factories:

mkdir spec/factories

Rails generators will automatically generate factory files for models inside this folder.

A generator for model automatically creating the following files:


Now lets load Factory bot configuration to rails test suite.

Add the following file:


and be sure to require this file in rails_helper.rb

require 'rspec/rails'
require 'database_cleaner'
require_relative 'support/database_cleaner'
require_relative 'support/factory_bot'  # <= here

You can see the following line commented

# Dir[Rails.root.join('spec', 'support', '**', '*.rb')].sort.each { |f| require f }

You can uncomment it to make all the factories available in your test suite. But I don’t recommend that to load all the files. We will be loading each factory whenver it is necessary.

Final rails_helper.rb file. We won’t use capybara for integration tests. So we are not adding database_cleaner configuration.

# This file is copied to spec/ when you run 'rails generate rspec:install'
require 'spec_helper'
ENV['RAILS_ENV'] ||= 'test'
require File.expand_path('../config/environment', __dir__)
# Prevent database truncation if the environment is production
abort('The Rails environment is running in production mode!') if Rails.env.production?
require 'rspec/rails'
require_relative 'support/factory_bot'

# Checks for pending migrations and applies them before tests are run.
# If you are not using ActiveRecord, you can remove these lines.
rescue ActiveRecord::PendingMigrationError => e
  puts e.to_s.strip
  exit 1
RSpec.configure do |config|
  # If you're not using ActiveRecord, or you'd prefer not to run each of your
  # examples within a transaction, remove the following line or assign false
  # instead of true.
  config.use_transactional_fixtures = false


  # Filter lines from Rails gems in backtraces.
  # arbitrary gems may also be filtered via:
  # config.filter_gems_from_backtrace("gem name")

A spec directory look something like this:



Model Specs

Lets generate a model spec. A model spec is used to test smaller parts of the system, such as classes or methods.

# RSpec also provides its own spec file generators
➜ rails generate rspec:model user
      create  spec/models/user_spec.rb
      invoke  factory_bot
      create    spec/factories/users.rb

Now run the rpsec command. That’s it. You can see the output from rspec.

➜ rspec

Pending: (Failures listed here are expected and do not affect your suite's status)

  1) Item add some examples to (or delete) /home/.../spec/models/user_spec.rb
     # Not yet implemented
     # ./spec/models/user_spec.rb:4

Finished in 0.00455 seconds (files took 1.06 seconds to load)
1 example, 0 failures, 1 pending

Lets discuss how to write a perfect model spec in the next lesson.

AWS SNS: How to send SMS to a topic, OTP SMS, promotional or transactional SMS

You can send SMS in 2 ways using AWS SNS.

  1. Subscribe to a topic that is already created in AWS SNS and send sms to all numbers who has the subscription.
  2. Send SMS directly to a mobile number.

You can find the following aws doc as a starting point from web and it describes how to create a topic, subscribe a topic and sending sms to the mobile numbers.

For the demonstration purpose, I use Ruby here.

  1. Subscribe to a topic and send SMS
require 'aws-sdk-sns'  # v2: require 'aws-sdk'

sns = 'us-west-2')

topic = sns.topic('arn:aws:sns:us-west-2:123456789:MySampleTextTopic')

  message: 'Hello!'

This assumes you already created a topic inside your aws console:

Goto AWS Console

  1. Open AWS SNS
  2. Goto Left side -> Mobile -> text messaging sms
  3. Create a topic

You can also follow the above link to perform an API to create topic, subscribe to a topic and send sms

2. Send SMS directly to a mobile number (eg: Send OTP SMS)

Either you can use aws console to send the sms or SNS API.

AWS console:

  1. Open AWS SNS
  2. Goto Left side -> Mobile -> text messaging sms
  3. We are using transactional text messages
  4. Goto publish text message
  5. Select transactional, add mobile number and publish it

Almost all cases we use an API for sending OTP SMS. For that follow the steps.

SNS API – Steps

gem install aws-sdk-sns 
require 'aws-sdk-sns'
otp = generate_otp
response = publish_sms(otp)
  • Generate an OTP
def generate_otp
  • Set SNS client
def set_sns_client
    @sns_client =
      region: ENV['AWS_SNS_REGION'],
      access_key_id: ENV['AWS_SNS_ACCESS_KEY'],
      secret_access_key: ENV['AWS_SNS_SECRET_KEY']

  • Set SNS client attributes
 def set_sns_client_attrs
                                     attributes: {
                                       'DefaultSenderID' => SENDER_ID,
                                       'DefaultSMSType' => SMS_TYPE

  • Publish SMS
def publish_sms(otp)
                          phone_number: @mobile_no,
                          message: "#{OTM_MSG} #{otp}"

Here ,

OTM_MSG: ‘Your OTP for login is’

SMS_TYPE : ‘Transactional’ or ‘Promotional’

If you want to send OTP, then it is ‘Transactional’. Else if you want to send some promotional sms of your product then it is ‘Promotional’

SENDER_ID: is the sms header that you already registered with TRAI.

The Steps to add Sender ID in AWS is given below:

For example suppose your sender id is: Zomato

Follow the steps to add our SENDER ID – Zomato to AWS SNS

  1. Sign in to the Amazon SNS console –
  2. On the navigation panel, choose Mobile, Text messaging (SMS).
  3. On the Mobile text messaging (SMS) page, in the Text messaging preferences section, choose Edit.
  4. On the Edit text messaging preferences page, in the Details section, do the following:
  5. For Default sender ID , enter the provided sender ID to be used (Zomato) as the default for all messages from your account.
  6. Choose Save changes.

If you don’t want to register sender id, then skip this method: set_sns_client_attrs and publish the sms. It take the sms as ‘Promotional’ and sender id will be 8 character random number. Amazon use this type of sms from International route and it costs you almost $0.02 (Rs. 1.5) per sms. Very high rate. So I recommend to register any sender id that resembles your product or company name, from Jio trueconnect (that is free, link given below) and use it in SNS.

If you don’t know how to register sender id, follow this:

For AWS SNS service, there is 2 way of sending sms.

  1. Local route
  2. International route

For local route the price is Rs. 0.20 per sms
For international route the price will be Rs 1.58 per sms – too high

by default AWS SNS use International route

If you are from India follow the TRAI registration
For considering local route we have to register our use case and message templates with TRAI .

So first register here:
as an enterprise / company with all company details and our purpose

These registration requirements are designed to reduce the number of unsolicited messages that Indian consumers receive, and to protect consumers from potentially harmful messages

You can Register in Jio for free:

The link to register:
Select Principal entity and continue

Recently Indian Govt made DLT Registration mandatory for sending sms.

Take Msg from AD-ZOMATO , here ZOMATO is 6 char sender id that we can give in the service provider and send sms before. But now we have to register this in DLT then only our service provider can use this.

After registering DLT we get an ENTITY ID. This entity id need to be attached in our’s otp service provider for sending otp msgs.

If you are using SNS service for the first time you should increase your SMS quota:

AWS says:

If you're new to SMS messaging with Amazon SNS, request a monthly SMS spending threshold that meets the expected demands of your SMS use case. By default, your monthly spending threshold is $1.00 (USD). You can request to increase your spending threshold in the same support case that includes your request for a sender ID

Because Amazon SNS is a distributed system, it stops sending SMS messages within minutes of the spending quota being exceeded. During this period, if you continue to send SMS messages, you might incur costs that exceed your quota.

Requesting increases to your monthly SMS spending quota for Amazon SNS:

Currently, Amazon SNS supports SMS messaging in the following AWS Regions:


Steps to reinstall rvm in ubuntu

Uninstall RVM – do the following

unset GEM_HOME

rvm implode

rm -rf ~/.rvm

rm -rf /usr/share/rvm

sudo rm /etc/profile.d/

sudo rm /etc/rvmrc

sudo rm ~/.rvmrc

vim ~/.zshrc  # or ~/.bash_profile related to machine/software you use and remove rvm related lines

Install RVM:

sudo apt-get install build-essential

unset rvm_path

sudo apt install gnupg2

Goto and add gpg keys to verify

gpg --keyserver hkp:// --recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3 7D2BAF1CF37B13E2069D6956105BD0E739499BDB
\curl -sSL | bash -s stable

If the following error occurs while adding keys:

➜ gpg --keyserver hkp:// --recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3 7D2BAF1CF37B13E2069D6956105BD0E739499BDB
gpg: connecting dirmngr at '/run/user/1000/gnupg/S.dirmngr' failed: IPC connect call failed
gpg: keyserver receive failed: No dirmngr

then do:

sudo apt-get update

then you will get some errors regarding the keys, add those keys to following command:

gpg --keyserver hkp:// --recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3 7D2BAF1CF37B13E2069D6956105BD0E739499BDB

then do:

\curl -sSL | bash -s stable

Make sure that your ~/.bash_profile or ~/.zshrc file contains the following lines to load rvm to the shell:

# RVM manual script for loading rvm to shell
[[ -s "$HOME/.rvm/scripts/rvm" ]] && . "$HOME/.rvm/scripts/rvm" 

After installing check RVM by:

➜ rvm list

# No rvm rubies installed yet. Try 'rvm help install'.

➜ rvm install 2.7.2
Searching for binary rubies, this might take some time.
Found remote file
Checking requirements for ubuntu.
Requirements installation successful.
ruby-2.7.2 - #configure
ruby-2.7.2 - #download
No checksum for downloaded archive, recording checksum in user configuration.
ruby-2.7.2 - #validate archive
ruby-2.7.2 - #extract
ruby-2.7.2 - #validate binary
ruby-2.7.2 - #setup
ruby-2.7.2 - #gemset created ~/.rvm/gems/ruby-2.7.2@global
ruby-2.7.2 - #importing gemset ~/.rvm/gemsets/global.gems..................................
ruby-2.7.2 - #generating global wrappers.......
ruby-2.7.2 - #gemset created ~/.rvm/gems/ruby-2.7.2
ruby-2.7.2 - #importing gemsetfile ~/.rvm/gemsets/default.gems evaluated to empty gem list
ruby-2.7.2 - #generating default wrappers.......

➜ rvm list
=* ruby-2.7.2 [ x86_64 ]

# => - current
# =* - current && default
#  * - default

➜ rvm gemset list

gemsets for ruby-2.7.2 (found in ~/.rvm/gems/ruby-2.7.2)
=> (default)

➜ rvm gemset create foobar
ruby-2.7.2 - #gemset created ~/.rvm/gems/ruby-2.7.2@foobar
ruby-2.7.2 - #generating foobar wrappers.......

➜ rvm gemset list              

gemsets for ruby-2.7.2 (found in ~/.rvm/gems/ruby-2.7.2)
=> (default)

➜ rvm gemset use foobar
Using ruby-2.7.2 with gemset foobar

➜ rvm gemset list           

gemsets for ruby-2.7.2 (found in ~/.rvm/gems/ruby-2.7.2)
=> foobar

➜ rvm list gemsets

rvm gemsets

   ruby-2.7.2 [ x86_64 ]
=> ruby-2.7.2@foobar [ x86_64 ]
   ruby-2.7.2@global [ x86_64 ]

For preserving the gemset for the current directory create .rvmrc file:

vim .rvmrc
# add this: rvm --rvmrc use foobar

If rvm is not loading into the shell after changing the terminal preferences, check the rvm_path env variable.

zsh: no such file or directory: /usr/share/rvm

If you don’t have that directory you must change the above path to a correct rvm installed path.

By default rvm installed in this path: ${HOME}/.rvm So you can add this path to rvm_path

Set it like:

export rvm_path="${HOME}/.rvm"

You can add this line into your ~/.zshrc OR ~/.bash_profile file.

You can check rvm env variables and info by:

env | grep rvm  
rvm info

Check ruby version by: ruby -v If ruby is not loading try to add the following line into your bash_profile:

export PATH=~/.rvm/gems/ruby-2.7.2/bin:$PATH # change version: ruby-2.7.2 to your installed version
source ~/.bash_profile OR source ~/.zshrc # whatever you use
ruby -v

Liferay 7.3: Add service builder to the portlet

Previously we created the portlet and service builder inside the eclipse workspace instead of creating a liferay workspace project inside eclipse workspce. So I face the following issues to add the service builder to my portlet (both of them are reside in eclipse workspace)

Could not run phased build action using Gradle distribution ''.
Build file '/home/abhilash/eclipse-workspace/register-emailbox/build.gradle' line: 32
A problem occurred evaluating root project 'register-emailbox'.
Project with path ':sitesService:sitesService-api' could not be found in root project 'register-emailbox'.

Many of them had this issue and you can search in this article of Liferay dev for creating service builder:

People commented like:

But no solution mentioned here

After some research I came to know that solving this issue, we need to create a portlet and a service builder inside the liferay workspace not eclipse workspace (We should create a liferay workspace project inside eclipse workspace).

Lets do that this time.

Click File -> New -> Liferay Workspace Project

Provide a Project Name and click on Finish

Next right click on the da-workspace, New -> Liferay Module Project

Provide the Project Name, then it automatically changes the Location

Provide the class name and project name

Deploy this service by clicking on the gradle section of IDE and double click on deploy

Deployed successfully

You can see the module os created inside our new Liferay Workspace: da-workspace

Jar file created

Copy this jar file and paste into the liferay server folder path given below:


You can see the server log like this:

2020-04-14 09:16:09.299 INFO  [fileinstall-/home/abhilash/liferay-ce-portal-tomcat-7.3.0-ga1-20200127150653953/liferay-ce-portal-7.3.0-ga1/osgi/modules][BundleStartStopLogger:39] STARTED com.emailbox_1.0.0 [1117]

Status -> STARTED

Now delete our old services. Goto the Goshell and uninstal the bundles:

Now goto the liferay and check our newly created portlet

Now lets repeat the steps for creating the service-builder from the previous article. But this time create it from da-workspace

File -> New -> Liferay Module Project

Services are created – For details check the previous article

Folder structure for the portlet and the service builder

Add the details as shown in the below screenshots (If any doubt check the previous article).

Do builder service and deploy

Copy this jar files one by one to the server’s deploy folder. First *api.jar and then *service.jar

Server logs:

liferay-ce-portal-tomcat-7.3.0-ga1-20200127150653953/liferay-ce-portal-7.3.0-ga1/osgi/modules][BundleStartStopLogger:39] STARTED com.siteservice.api_1.0.0 [1118]

liferay-ce-portal-7.3.0-ga1/osgi/modules][BundleStartStopLogger:39] STARTED com.siteservice.service_1.0.0 [1119]

Check the database, you can see the Site_ Table and columns are created.

Now add the service builder dependancy to the portlet

Add this two lines in the build.gradle file

Right click on showEmailBox portlet and gradle -> refresh gradle project

DONE! You are successfully binded the service builder to your portlet.

now add the following to your portal class file above the doView function

private SiteLocalService _siteLocalService;

now you can use the following default functions provided by liferay on the service.


But what is we needed to fetch suppose some sites which has particular site_id Or fetch all sites which has registered after this time etc?

For all these custom query to mysql db, we needed to create a custom finder methods. So lets create one.

Open service.xml of `siteService-service`

Click on Finders and add Name and Type

Click on Finder column and add the db column to find

Click on Source, you can see the finder is added

Double click on the buildService to build the service

Now we can add custom finder findBySiteId to this service.


package com.siteservice.service.impl;

import com.liferay.portal.aop.AopService;
import com.siteservice.model.Site;
import com.siteservice.service.base.SiteLocalServiceBaseImpl;

import java.util.List;

import org.osgi.service.component.annotations.Component;

	property = "",
	service = AopService.class
public class SiteLocalServiceImpl extends SiteLocalServiceBaseImpl {

	public List<Site> findBySiteId(long site_id) {
		return sitePersistence.findBySiteId(site_id);

Now do the buildService for siteService. Then Gradle -> Refresh and deploy the service. Copy this jar files one by one to the server’s deploy folder. First *api.jar and then *service.jar

Refresh Gradle project for the portlet – showEmailBox

Add the following to the doView function of the portlet

Site site = _siteLocalService.findBySiteId(2233).get(0);
System.out.println("We got the site: ---------");

and don’t forget to create a site entry in the database with id: 2233

mysql> insert into Site_ (id_, site_id, name, register_from_date, register_to_date, created_at, updated_at) values (1, 2233, 'Site 2020', '2020-01-01', '2020-06-19', CURDATE(), CURDATE());

deploy the portlet and check you are getting the site in the server console.

that’s it for now, will see in the next article.

Liferay 7.3: Create custom database services (service-builder)


Open the IDE. Goto File -> New -> Liferay Module Project

Select `service-builder` as Template

Click Next. Provide the package name and click finish

After that you can see two folders are created (*-api and *-service) inside your workspace.

And three folders in the IDE

Open siteService-service and click on service.xml . Click on the Entities and delete the default Foo column

And then add the Entity named Site . It is just an Entity, that connects to the table.

Click on the Site Entity and provide the table name

Add the Table Name

Add the columns as many as you want.

Select the column type from here:

Click on the Source Tab and you can see in the service.xml details of all columns that added.

Double click on the buildService to build the new service and double click on the deploy to deploy the service.

Now click on the down arrow and gradle -> refresh project. You can see the bundles created.

And the .jar bundles inside the osgi modules

Copy this *-api.jar file into the deploy folder of the server.


and then copy the *-service.jar into the same folder

You can see these are processing and started in the server logs.

INFO  [][AutoDeployDir:263] Processing sitesService.api.jar

~/liferay-ce-portal-tomcat-7.3.0-ga1-20200127150653953/liferay-ce-portal-7.3.0-ga1/osgi/modules][BundleStartStopLogger:39] STARTED sitesService.api_1.0.0 [1115]

[][AutoDeployDir:263] Processing sitesService.service.jar

~/liferay-ce-portal-tomcat-7.3.0-ga1-20200127150653953/liferay-ce-portal-7.3.0-ga1/osgi/modules][BundleStartStopLogger:39] STARTED sitesService.service_1.0.0 [1116]

Now check the database, if the Site_ table with all columns are created or not

You can see the table and columns are created. In the next topic we discuss about adding services to this service builder.

Liferay 7.3: Create a custom MVC portlet

What is a portlet?

A portlet is fragment on a webpage as web application and is used with portlets on the same webpage.

When you access a web site, you interact with an application. That application may be simple: it may only show you information, such as an article. The application may be complex, including forms, sending data etc. These applications run on a platform that provides application developers the building blocks they need to make applications.

If there are so many implementations of MVC frameworks in Java, why did Liferay create yet another one?

Liferay MVC provides these benefits:

It’s lightweight, as opposed to many other Java MVC frameworks.
There are no special configuration files that need to be kept in sync with your code.
It’s a simple extension of GenericPortlet.
You avoid writing a bunch of boilerplate code, since Liferay’s MVC framework simply looks for some pre-defined parameters when the init() method is called.
The controller can be broken down into MVC command classes, each of which handles the controller code for a particular portlet phase (render, action, and resource serving phases).
Liferay’s portlets use it. That means there are plenty of robust implementations to reference when you need to design or troubleshoot your Liferay applications.

Each portlet phase executes different operations:


The init()  method is called by the portlet container during deployment and reads init parameters defined in portlet.xml file. The Portlet interface exposes the init method as:  void init (PortletConfig config) throws PortletException
The PortletConfig interface is  to retrieve configuration  from the portlet definition in the deployment descriptor. The portlet can only read the configuration data. The configuration information contains the portlet name, the portlet initialization parameters, the portlet resource bundle and the portlet application context.


Generates the portlet’s contents based on the portlet’s current state. When this phase runs on one portlet, it also runs on all other portlets on the page. The Render phase runs when any portlets on the page complete the Action or Event phases.

In this phase portlet generates content and renders on webpage.

The render phase is called in below cases:
1. The page that contains portlet is rendered on web page
2. After completing Action Phase
3. After completing Event Processing phase

below is example:

<portlet:renderURL var=“loadEmployees”> <portlet:param name=”mvcPath”
value=”/WEB-INF/view/empList.jsp” /> </portlet:renderURL>
<a href=”<%=loadEmployees%>”>Click here</a>


In response to a user action, performs some operation that changes the portlet’s state. The Action phase can also trigger events that are processed by the Event phase. Following the Action phase and optional Event phase, the Render phase then regenerates the portlet’s contents.

 its result of user actions such as add,edit, delete

  1. only one portlet can be entered into action phase for a request in a portlet container
  2. Any events triggered during the Action phase are handled during the Event phaseof the portlet lifecycle. Events can be used when portlets want to communicatewith each other. The Render phase will be called when all events have been handled.


Processes events triggered in the Action phase. Events are used for IPC. Once the portlet processes all events, the portal calls the Render phase on all portlets on the page.
Resource-serving: Serves a resource independent from the rest of the lifecycle. This lets a portlet serve dynamic content without running the Render phase on all portlets on a page. The Resource-serving phase handles AJAX requests.


You can see more details and clarify your doubts by checking the following docs:

Now we get into the action.

Step 1: Open Developer Studio and goto File -> New -> Liferay Module Project

Add Project Name, select mvc-portlet

Add class Name and package name and click finish

Step 2: Take Gradle task from right side section and double click on the deploy of created portlet

You can check the created portlet in the Studio’s workspace folder as above

The controller class of the portlet

Step 3: We will be changing the Class file and view file and deploy the changes and check

Add some code snnippet inside the above java Class, so that class look like this:

package com.register.portlet;

import com.register.constants.CheckRegisterPortletKeys;


import com.liferay.portal.kernel.portlet.bridges.mvc.MVCPortlet;

import javax.portlet.Portlet;
import javax.portlet.PortletException;
import javax.portlet.RenderRequest;
import javax.portlet.RenderResponse;

import org.osgi.service.component.annotations.Component;

 * @author abhilash
	immediate = true,
	property = {
		"" + CheckRegisterPortletKeys.CHECKREGISTER,
	service = Portlet.class
public class CheckRegisterPortlet extends MVCPortlet {
	public void doView(RenderRequest renderRequest, RenderResponse renderResponse) 
		   throws IOException, PortletException {
		System.out.println("inside my check registration logic controller");
		super.doView(renderRequest, renderResponse);

Change the view.jsp as above

Step 4: Deploy again so that you can see the jar file is created as below:

The jar file created

Copy the jar file into this tomcat server folder, so that it can pick the package

Step 5: You can see the package status in the Gogo shell Liferay provides, goto the http://localhost:8080 and check inside the Configuration section Gogo Shell. Type command: lb

See the status – Installed. It should be ACTIVE after we deploy it.

Here are the list of osgi lifecycle status:

Step 6: Handle the Errors if any

I am getting some issues with this creation, lets see what is the problem.

I don’t have any idea why I am getting this. After some research I tried to add the module that is missing here, but no luck. Then I realised we are on Liferay 7.3 and See the pic of the IDE there we selected 7.2 version because thats the latest version available there. Hmm…So…yeahh that may be the issue here. You got that!

So Update your IDE and create the package again.

Now it Works!

And check our newly created portlet in the right side section (inside Widget) of Liferay Site.

You can see this messge that we wrote inside the Class inside my check registration logic controller in your server console in IDE. And this message This portlet is created by Abhilash inside the Portlet.

Congrats .. You have created your first custom portlet in Liferay.

Liferay 7.3: Developing custom themes

You can see the details for setting up themes from here:

Goto your workspace folder (our developer studio workspace ~/eclipse-workspace):

$ cd ~/eclipse-workspace
$ nvm use 10.5
$ npm install -g generator-liferay-theme
$ npm install -g yo gulp
$ yo liferay-theme

Provide theme name, id, liferay version and font information

? What would you like to call your theme? Theme Moon
? What id would you like to give to your theme? theme-moon
? Which version of Liferay is this theme for? 7.3
? Would you like to add Font Awesome to your theme? No
The project has been created successfully.

 Now we will invoke gulp init for you, to configure your deployment

Remember, that you can change your answers whenever you want by 
running gulp init again.

? Select your deployment strategy (Use arrow keys)
❯ Local App Server   // select this
  Docker Container 

? Select your deployment strategy Local App Server
? Enter the path to your app server directory: /home/abhilash/liferay-ce-portal-tomcat-7.3.0-ga1-20200127150653953/liferay-ce-portal-7.3.0-ga1/tomcat-9.0.17
? Enter the url to your production or development site: http://localhost:8080

Run the command below from the theme’s root folder to build the files:

$ cd theme-moon
$ gulp build   # this creates the build folder

Now do the following changes to edit the created theme.

** Create a new /src/templates/ folder and copy portal_normal.ftl from the build/templates/ folder into it.

Configure the theme to extend the Atlas theme. Add a clay.scss file to the theme’s /src/css/ folder and add the import shown below:

@import "clay/atlas";

Create an _imports.scss file in the /src/css/ folder and add the imports shown below to it. This includes the default imports and replaces the clay/base-variables with the Atlas base variables:

@import "bourbon";

@import "mixins";

@import "compat/mixins";

@import "clay/atlas-variables";

You’ve generated the theme, prepared it for development, and configured it to extend the Atlas theme

Customizing the Header and Logo of your theme

Open portal_normal.ftl and replace the <header>...</header> element and contents with the updated code snippet below. This updates the structure slightly, making the banner expand the full width of the Header, and adds a new header_css_class variable to the class attribute. This variable is defined in a later step.

<header class="${header_css_class}">
	<div class="container-fluid" id="banner" role="banner">
		<a class="${logo_css_class}" href="${site_default_url}" title="<@liferay.language_format arguments="${site_name}" key="go-to-x" />">
			<img alt="${logo_description}" height="${site_logo_height}" src="${site_logo}" width="${site_logo_width}" />
			<#if show_site_name>

		<#if has_navigation>
			<#include "${full_templates_path}/navigation.ftl" />

Replace the <div class="container-fluid" id="wrapper"> element with the updated code below to remove some margins and padding:

<div class="container-fluid mt-0 pt-0 px-0" id="wrapper">

And move the wrapper down, and place it directly above the <section id="content"> element:

<div class="container-fluid mt-0 pt-0 px-0" id="wrapper">
  <section id="content">

The logo’s height is retrieved with the ${site_logo_height} variable. The height of the logo is a bit too large for the this theme, so you must adjust it. Remove the width attribute from the logo’s image so it defaults to auto:

<img alt="${logo_description}" height="${site_logo_height}" src="${site_logo}" />

Create init_custom.ftl in your theme’s /src/templates/ folder and assign the logo’s site_logo_height variable to the value below:

<#assign site_logo_height = 56 />

Assign the new header_css_class variable you added in step one to the value below:

#assign header_css_class = 
"navbar navbar-expand-md navbar-dark flex-column flex-md-row bd-navbar" 

This applies Bootstrap and Clay utility classes to provide the overall look and feel of the Header. Assigning the classes to a variable keeps portal_normal clean and makes the code easy to maintain. If you want to update the classes, you just have to modify the variable (e.g. header_css_class = header_css_class + " my-new-class").

Add the code snippet below to update the logo_css_class variable to use Bootstrap’s navbar-brand class:


Before you upload the theme to see what it looks like so far, you must create a theme thumbnail so you can identify it. Create a  thumbnail.png and replace the default from the /src/images/ folder. Note that its dimensions are 480px by 270px. These dimensions are required to display the theme thumbnail properly.

DEVELOPER MODE (If not enabled you may face CSS / JS loading issues )

The theme isn’t complete yet, but you’ll deploy what you have so you can replace the default logo with the your logo. Enable Developer Mode before deploying your theme, so the theme’s files are not cached for future deployments.

Once I faced CSS loading issue in my AWS Liferay site for one theme. After a lot of research I found that, the server doesn’t have file and not enabled the so called Developer Mode

Custom CSS Loading in Liferay

My custom _import.scss almost look like this:

/* These inject tags are used for dynamically creating imports for themelet styles, you can place them where ever you like in this file. */

/* inject:imports */

/* endinject */

/* This file allows you to override default styles in one central location for easier upgrade and maintenance. */

@import "bourbon";

@import "mixins";

@import "compat/mixins";

@import "clay/atlas-variables";

@import "./style.scss";

@import "./innerstyle.scss";

@import "./mixedslider.scss";


Liferay loads this css file in HTML like as follows:

<link class="lfr-css-file" data-senna-track="temporary" href="http://localhost:8080/o/myTheme-theme/css/main.css?browserId=other&themeId=myTheme_WAR_myThemetheme&languageId=en_US&b=7301&t=1588754322000" id="liferayThemeCSS" rel="stylesheet" type="text/css" />

When clicking on this file, I get different css as follows instead of the _import.scss file listed above.

.loadingmask-message{background:transparent;border-width:0;display:block;height:1em;margin-left:auto;margin-right:auto;position:relative;text-align:left;width:1em}.loadingmask-message .loadingmask-message-content{-webkit-animation:loading-animation 1.2s infinite ease-out;animation:loading-animation 1.2s 

This is because of I was not enabled the ‘Developer Mode’ in portal.ext file.

After enabling it as below, my _import.scss styles shows up in Liferay’s main.css file.

Create a file in your server’s root folder if it doesn’t exist.

Add the line below to it:

Start the server, if it’s not already started, and deploy the theme with the command below:

$ gulp deploy
[20:34:49] Finished 'plugin:deploy' after 32 ms
[20:34:49] Finished 'deploy:war' after 32 ms
[20:34:49] Finished 'deploy' after 4.78 s


Open the Control Menu and navigate to Site Builder → Pages. Click the Gear icon next to Public Pages to open the configuration menu. Under the Look and Feel tab, scroll down and click the Change Current Theme button and select the Lunar Resort Theme. Scroll to the Logo heading, click the Change button, upload the new-logo.png logo, and click the Save button to apply the theme and logo.



And at last how to import this theme so that your tomcat server runs it?

Goto your Developer studio
Goto File -> Import -> Import gradle project -> select the path of this theme root directory -> Done!

Click on the File -> Import

Select the created theme path

Right click on the server and restart it. OR right click on the theme after draging it to the server and click on restart

Then goto http://localhost:8080/

click on Settings icon on top -> Under theme select

Define a specific look and feel for this page.

New theme added

Click on the new theme and click SAVE button. You can see your new theme activated. Congrats!

Liferay 7.3: Connect with MYSQL database

Go inside the downloaded liferay portal folder. And start the tomcat server

cd ~/liferay-ce-portal-tomcat-7.3.0-ga1-20200127150653953/liferay-ce-portal-7.3.0-ga1/tomcat-9.0.17/bin

Goto http://localhost:8080/ and start setting up liferay configurations as follows:

Do the basic configuration and change database to mysql

Mysql database is recommended fot staging and production environment.

JDBC Driver Class Name: com.mysql.cj.jdbc.Driver

If you get the error like this, some of your provided mysql configuration went wrong. Check your mysql username and password.

If the given mysql configuration is correct, then you have to check the Liferay DB exists in your Mysql database. Else create the DB `lportal` as follows:

Create database ‘lportal’

Now click on the Configure button, you will be ready to go

Open ‘~/liferay-ce-portal-tomcat-7.3.0-ga1-20200127150653953/liferay-ce-portal-7.3.0-ga1/’ file and you can see the above configurations added by liferay.

You can see the above server log in Dev studio. Here mentioned about which property files loads during the start and picking MYSQL DB, creating tables for first time in `lportal` database, inserting data to the table

You can see the following tables populated in the database lportal

mysql> show tables;
| Tables_in_lportal              |
| AMImageEntry                   |
| AccountEntry                   |
| AccountEntryOrganizationRel    |
| AccountEntryUserRel            |
| AccountRole                    |
| Account_                       |
| Address                        |
| AnnouncementsDelivery          |
| AnnouncementsEntry             |
| AnnouncementsFlag              |
| AppBuilderApp                  |
| AppBuilderAppDeployment        |
| AssetAutoTaggerEntry           |
| AssetCategory                  |
| AssetCategoryProperty          |
| AssetDisplayPageEntry          |
| AssetEntries_AssetCategories   |
| AssetEntries_AssetTags         |
| AssetEntry                     |
| AssetEntryAssetCategoryRel     |
| AssetEntryUsage                |
| AssetLink                      |
| AssetListEntry                 |
| AssetListEntryAssetEntryRel    |
| AssetListEntrySegmentsEntryRel |
| AssetListEntryUsage            |
| AssetTag                       |
| AssetVocabulary                |
| Audit_AuditEvent               |
| BackgroundTask                 |
| BatchEngineExportTask          |
| BatchEngineImportTask          |
| BlogsEntry                     |
| BlogsStatsUser                 |
| BrowserTracker                 |
| CTCollection                   |
| CTEntry                        |
| CTMessage                      |
| CTPreferences                  |
| CTProcess                      |
| CTSContent                     |
| Calendar                       |
| CalendarBooking                |
| CalendarNotificationTemplate   |
| CalendarResource               |
| ChangesetCollection            |
| ChangesetEntry                 |
| ClassName_                     |
| Company                        |
| CompanyInfo                    |
| Configuration_                 |
| Contact_                       |
| Contacts_Entry                 |
| Counter                        |
| Country                        |
| DDLRecord                      |
| DDLRecordSet                   |
| DDLRecordSetVersion            |
| DDLRecordVersion               |
| DDMContent                     |
| DDMDataProviderInstance        |
| DDMDataProviderInstanceLink    |
| DDMFormInstance                |
| DDMFormInstanceRecord          |
| DDMFormInstanceRecordVersion   |
| DDMFormInstanceVersion         |
| DDMStorageLink                 |
| DDMStructure                   |
| DDMStructureLayout             |
| DDMStructureLink               |
| DDMStructureVersion            |
| DDMTemplate                    |
| DDMTemplateLink                |
| DDMTemplateVersion             |
| DEDataDefinitionFieldLink      |
| DEDataListView                 |
| DLContent                      |
| DLFileEntry                    |
| DLFileEntryMetadata            |
| DLFileEntryType                |
| DLFileEntryTypes_DLFolders     |
| DLFileRank                     |
| DLFileShortcut                 |
| DLFileVersion                  |
| DLFileVersionPreview           |
| DLFolder                       |
| DLOpenerFileEntryReference     |
| DLSyncEvent                    |
| EmailAddress                   |
| ExpandoColumn                  |
| ExpandoRow                     |
| ExpandoTable                   |
| ExpandoValue                   |
| ExportImportConfiguration      |
| FragmentCollection             |
| FragmentEntry                  |
| FragmentEntryLink              |
| FriendlyURLEntry               |
| FriendlyURLEntryLocalization   |
| FriendlyURLEntryMapping        |
| Group_                         |
| Groups_Orgs                    |
| Groups_Roles                   |
| Groups_UserGroups              |
| HtmlPreviewEntry               |
| IM_MemberRequest               |
| Image                          |
| JournalArticle                 |
| JournalArticleLocalization     |
| JournalArticleResource         |
| JournalContentSearch           |
| JournalFeed                    |
| JournalFolder                  |
| KBArticle                      |
| KBComment                      |
| KBFolder                       |
| KBTemplate                     |
| KaleoAction                    |
| KaleoCondition                 |
| KaleoDefinition                |
| KaleoDefinitionVersion         |
| KaleoInstance                  |
| KaleoInstanceToken             |
| KaleoLog                       |
| KaleoNode                      |
| KaleoNotification              |
| KaleoNotificationRecipient     |
| KaleoTask                      |
| KaleoTaskAssignment            |
| KaleoTaskAssignmentInstance    |
| KaleoTaskForm                  |
| KaleoTaskFormInstance          |
| KaleoTaskInstanceToken         |
| KaleoTimer                     |
| KaleoTimerInstanceToken        |
| KaleoTransition                |
| Layout                         |
| LayoutBranch                   |
| LayoutClassedModelUsage        |
| LayoutFriendlyURL              |
| LayoutPageTemplateCollection   |
| LayoutPageTemplateEntry        |
| LayoutPageTemplateStructure    |
| LayoutPageTemplateStructureRel |
| LayoutPrototype                |
| LayoutRevision                 |
| LayoutSEOEntry                 |
| LayoutSEOSite                  |
| LayoutSet                      |
| LayoutSetBranch                |
| LayoutSetPrototype             |
| ListType                       |
| Lock_                          |
| MBBan                          |
| MBCategory                     |
| MBDiscussion                   |
| MBMailingList                  |
| MBMessage                      |
| MBStatsUser                    |
| MBThread                       |
| MBThreadFlag                   |
| MDRAction                      |
| MDRRule                        |
| MDRRuleGroup                   |
| MDRRuleGroupInstance           |
| Marketplace_App                |
| Marketplace_Module             |
| MembershipRequest              |
| OA2Auths_OA2ScopeGrants        |
| OAuth2Application              |
| OAuth2ApplicationScopeAliases  |
| OAuth2Authorization            |
| OAuth2ScopeGrant               |
| OrgGroupRole                   |
| OrgLabor                       |
| Organization_                  |
| PasswordPolicy                 |
| PasswordPolicyRel              |
| PasswordTracker                |
| Phone                          |
| PluginSetting                  |
| PollsChoice                    |
| PollsQuestion                  |
| PollsVote                      |
| PortalPreferences              |
| Portlet                        |
| PortletItem                    |
| PortletPreferences             |
| QUARTZ_CALENDARS               |
| QUARTZ_JOB_DETAILS             |
| QUARTZ_LOCKS                   |
| QUARTZ_TRIGGERS                |
| RatingsEntry                   |
| RatingsStats                   |
| ReadingTimeEntry               |
| RecentLayoutBranch             |
| RecentLayoutRevision           |
| RecentLayoutSetBranch          |
| Region                         |
| Release_                       |
| Repository                     |
| RepositoryEntry                |
| ResourceAction                 |
| ResourcePermission             |
| Role_                          |
| SAPEntry                       |
| SegmentsEntry                  |
| SegmentsEntryRel               |
| SegmentsEntryRole              |
| SegmentsExperience             |
| SegmentsExperiment             |
| SegmentsExperimentRel          |
| ServiceComponent               |
| SharingEntry                   |
| SiteFriendlyURL                |
| SiteNavigationMenu             |
| SiteNavigationMenuItem         |
| SocialActivity                 |
| SocialActivityAchievement      |
| SocialActivityCounter          |
| SocialActivityLimit            |
| SocialActivitySet              |
| SocialActivitySetting          |
| SocialRelation                 |
| SocialRequest                  |
| Subscription                   |
| SystemEvent                    |
| Team                           |
| Ticket                         |
| TrashEntry                     |
| TrashVersion                   |
| UserGroup                      |
| UserGroupGroupRole             |
| UserGroupRole                  |
| UserGroups_Teams               |
| UserIdMapper                   |
| UserNotificationDelivery       |
| UserNotificationEvent          |
| UserTracker                    |
| UserTrackerPath                |
| User_                          |
| Users_Groups                   |
| Users_Orgs                     |
| Users_Roles                    |
| Users_Teams                    |
| Users_UserGroups               |
| ViewCountEntry                 |
| VirtualHost                    |
| WebDAVProps                    |
| Website                        |
| WikiNode                       |
| WikiPage                       |
| WikiPageResource               |
| WorkflowDefinitionLink         |
| WorkflowInstanceLink           |
262 rows in set (0.00 sec)


Here is the list of Users created. We are selected `Add Sample data` option at the time configuration. That is why we can see lot of users with my domain is listed

Login with

Email: provide the email given at the time of register

Password: test (default password for version 7.3)

If you are using Liferay 7.2 version, then this default password may not work. In this version liferay generates a random password, that you can check in the log file as below:

cd ~/Downloads/liferay-ce-portal-tomcat-7.2.1-ga2-20191111141448326/liferay-ce-portal-7.2.1-ga2/tomcat-9.0.17/logs/ && tail -f catalina.out

Search word password. You can get it.

After login you can see this terms of use page

Then a Password remainder page

DONE! Congrats, you can see the admin home page with all controls

changing to new db from old db

Add the new db name ‘liferay721’ to


Restart the server. Server Log:

Starting Liferay Community Edition Portal 7.2.1 CE GA2 (Mueller / Build 7201 / November 12, 2019)

2020-05-08 14:45:53.921 INFO  [main][StartupHelper:75] There are no patches installed
2020-05-08 14:45:54.000 WARN  [main][ReleaseLocalServiceImpl:238] Table 'liferay721.Release_' doesn't exist
2020-05-08 14:45:54.003 INFO  [main][ReleaseLocalServiceImpl:129] Create tables and populate with default data

Issues you may face during the setup

Once I created a DB names liferay_7_3_1 and the setup wizard finishes successfully and message shown to restart the server for get into the portal.

When I restart it I received the following issue:

An unexpected system error occurred.


Database is not setup properly. Check the DB name. Use characters as DB name if possible and start the server. If proper DB setup is done, then you can see 250 – 280 tables in database

and the following tables were created in the database named liferay_7_3_1

Only 155 rows

| Tables_in_liferay_7_3_1        |
| AccountEntry                   |
| AccountEntryOrganizationRel    |
| AccountEntryUserRel            |
| Website                        |
| WikiNode                       |
| WikiPage                       |
| WikiPageResource               |
| WorkflowDefinitionLink         |
| WorkflowInstanceLink           |
155 rows in set (0.00 sec)

Then I renamed the DB to liferay731. Then it works! So beware of underscore characters in your DB name.

Another error is shown when the server is not able to connect to your database, or database is not created that is specified as in your or

Create a database specified in properties file to get rid of the issue. Else check given DB permissions are correct

Liferay 7.3: Configure IDE

Step 1. Download it from

Download from here:

Extract it to your /home directory

Step 2. run the installer


You can see the steps involving the process below.

Select java runtime

After install Open it. Double click on the following file:


you can see the welcome page. Close it.

Click on Workbench to start on

This is how Liferay Studio Workspace home page look like

You can create liferay plugins / projects etc from here.

Step 3. Click on right corner first button and open perspective.

select Liferay plugins

Open Perspective

Step 4. Left bottom corner there is ‘Servers’ Tab

right click on it and select ‘New’ -> ‘Server’

Select ‘Liferay inc’ -> Liferay 7.x and click next

Add the tomcat server to your SDK to control the server from SDK. This helps you to see the server logs and other live status in IDE

Select the tomcat server path from your downloaded liferay portal

Make sure you are using the timestamp folder in the path of the server, else it not gonna work. It will show you an error like this:

If you start the tomcat server and try to access it without the timestamp PATH

You can add an existing resources created if available like theme etc to the server. If you don’t have any resources don’t worry. We are going to cover this in next chapter.

Click on the server name on the bottom left corner and you can see the configurations of the server.

Server Started. You can see the server logs in the console

LifeRay: What is Liferay? How to install it?

What is liferay?

With liferay we can create any number of custom sites. We can easily create sections like web content, blog content etc and share between those
sites. We can drag and drop sections to create website parts. So it is easy to use for admin users to add sections and drag to create pages.

“Liferay Portal is an open source enterprise web platform for building business solutions that deliver immediate results and long-term value. Liferay Portal started out as a personal development project in 2000 and was open sourced in 2001.”


Download liferay from:

Or by command line:


Here we are going to install liferay version 7.3



check the readme file in (after downloading liferay portal)

~/Downloads/liferay-ce-portal-tomcat-7.3.1-ga2-20191111141448326/liferay-ce-portal-7.3.0-ga1/ folder

After that Download and install Java (JDK) 8 (if necessary) in your local environment.

OpenJDK 8

Java 8 is the current Long Term Support version and is still widely supported, though public maintenance ends in January 2019. To install OpenJDK 8, execute the following command:

sudo apt install openjdk-8-jdk
➜ java -version
openjdk version "1.8.0_232"
OpenJDK Runtime Environment (build 1.8.0_232-8u232-b09-0ubuntu1~18.04.1-b09)
OpenJDK 64-Bit Server VM (build 25.232-b09, mixed mode)

Extract the liferay portal .zip file. Copy the folder liferay-ce-portal-7.3.0-ga1 to your home path. And go inside the folder path as shown below:

cd ~/liferay-ce-portal-7.3.0-ga1/tomcat-9.0.17/bin

and do:

Start Tomcat server:


Using CATALINA_BASE:   /home/abhi/liferay-ce-portal-7.3.0-ga1/tomcat-9.0.17
Using CATALINA_HOME:   /home/abhi/liferay-ce-portal-7.3.0-ga1/tomcat-9.0.17
Using CATALINA_TMPDIR: /home/abhi/liferay-ce-portal-7.3.0-ga1/tomcat-9.0.17/temp
Using JRE_HOME:        /usr/lib/jvm/java-8-openjdk-amd64
Using CLASSPATH:       /home/abhi/liferay-ce-portal-7.3.0-ga1/tomcat-9.0.17/bin/bootstrap.jar:/home/abhi/liferay-ce-portal-7.3.0-ga1/tomcat-9.0.17/bin/tomcat-juli.jar
Tomcat started.

Restart command for Tomcat server:

sh && sh

Goto http://localhost:8080/

Go with default hypersonic DB if you are in testing environment.

Restart the Tomcat server

sh && sh

and goto http://localhost:8080

If the server is not started properly, you can check the logs here:

cd ~/liferay-ce-portal-7.3.0-ga1/tomcat-9.0.17/logs/ && tail -f catalina.out

I faced the following issue in new 7.3 version with mysql db which I didn’t face with 7.2 version with Hypersonic DB. My mysql db schema is not proper to version 7.3.

  __    ____________________  _____  __
   / /   /  _/ ____/ ____/ __ \/   \ \/ /
  / /    / // /_  / __/ / /_/ / /| |\  /
 / /____/ // __/ / /___/ _, _/ ___ |/ /
/_____/___/_/   /_____/_/ |_/_/  |_/_/

Starting Liferay Community Edition Portal 7.3.0 CE GA1 (Athanasius / Build 7300 / January 20, 2020)

2020-03-23 12:22:13.798 INFO  [main][StartupHelperUtil:99] There are no patches installed
You must first upgrade the portal to the required schema version 8.1.0
2020-03-23 12:22:13.860 ERROR [main][MainServlet:300] java.lang.RuntimeException: You must first upgrade the portal to the required schema version 8.1.0
java.lang.RuntimeException: You must first upgrade the portal to the required schema version 8.1.0
	at com.liferay.portal.internal.servlet.MainServlet.init(

You’re presented a Basic Configuration page. Complete the configuration options. Liferay Portal uses an embedded database (HSQL) to make installation fast and easy. This database is not ready for production, so consider configuring a production-ready database (e.g., MySQL) if you plan on doing more than just exploring/testing. Agree to the terms and conditions, create a password, and configure a security question/answer.

You can now use Liferay Portal!

You can check all the projects from Liferay:

Get started from here:

Liferay IDE:

Liferay SDK:

“Liferay Plugin SDK is a development environment allows you to develop plugins for Liferay of all types such as Portlet, Themes, Layout Templates. The Liferay Plugin SDK is based on the Apache Ant tool and it can be integrated with all the common IDEs or used directly from the command line by executing a set of predefined commands (targets, in Ant’s nomenclature). In this tutorial I show how to configure the Plugin SDK in the Eclipse IDE”

Check this link (this doc is for version 6.1) :

LifeRay DXP:

This is the paid version from Liferay. With Liferay DXP you can implement not just static site, but something like today’s frontend fromeworks do (updating the DOM according to framework and spontaneous data change). Also you get the liferay support according to your plan.