This website uses cookies to ensure you get the best experience. Learn More.
Liferay DXP is a flexible platform which has been developed initially to support developers to easily implement business automation needs. We always face the question “what if i have a custom business requirements, how easy can we implement that if we adapt Liferay?”, in this blog we will discover the ease of customizing Liferay to implement business needs smoothly.
In order to get the most understanding of this blog, this blog has been authored as a user guide which walks you through the steps to build the custom application, for that you will need the following to be installed / configured on your computer:
Liferay Developer Studio / IntelliJ Studio with Liferay Plugin.
Liferay bundle to be installed and configured.
In this blog i will be using IntelliJ Integrated Development Environment
i
In order to start working with liferay customization you will need to create your Liferay Workspace Project, this workspace will be the main host for your custom bundles.
The following are the steps to create your workspace project:
Open IntelliJ
Click on File -> New -> Project.
Select Liferay then Liferay Gradle Workspace.
Name your project, for this Exercise, we will name the workspace as Exercise-Project.
From the project file structure, select modules and create a new Liferay Module.
Provide the package name “com.liferay.training.bookmarks”.
Select service-builder from the module types and click Next.
Provide the module name “services”.
Congratulations! You have just created your project successfully, now let's look at the project structure that you have just created and try to understand it.
In this section we will try to understand the main files and folders that you will be interested in while reading this blog.
Figure (01)
Services-api: This folder will contain the auto generated interfaces required to identify your services signatures and data envelopes, most probably you will not use this folder to do any customizations.
Services-service: This is where you will be building your Local Services and Remote Services, and defining your data entities.
Services-service -> service.xml: This is where you will be defining your data tables which are required for to be developed bookmarks.
In order to build your data structure for your bookmarks application, all what you need to do is to define your data schema in the service.xml, once you decided about the schema and you specify that in the xml file, Liferay will generate the services code and database tables using a Gradle process.
The following is a simple example of the data structure schema:
<service-builder dependency-injector="ds" package-path="com.liferay.training.bookmarks">
<namespace>BOOKMARK</namespace>
<entity local-service="true" name="Bookmark" remote-service="true" uuid="true">
<!-- PK fields -->
<column name="bookmarkId" primary="true" type="long" />
<!-- Group instance -->
<column name="groupId" type="long" />
<!-- Audit fields -->
<column name="companyId" type="long" />
<column name="userId" type="long" />
<column name="userName" type="String" />
<column name="createDate" type="Date" />
<column name="modifiedDate" type="Date" />
<!-- Other fields -->
<column name="title" type="String" />
<column name="description" type="String" />
<column name="url" type="String" />
<!-- Order -->
<order by="asc">
<order-column name="createDate" />
</order>
<!-- Finder methods -->
<finder name="UserId" return-type="Collection">
<finder-column name="userId" />
</finder>
<finder name="BookmarkId" return-type="Collection">
<finder-column name="bookmarkId" />
<!-- References -->
<reference entity="Group" package-path="com.liferay.portal"></reference>
<reference entity="AssetEntry" package-path="com.liferay.portlet.asset"></reference>
<reference entity="AssetLink" package-path="com.liferay.portlet.asset"></reference>
<reference entity="AssetTag" package-path="com.liferay.portlet.asset"></reference>
</entity>
</service-builder>
You have noticed that we have added Audit fields which we will be using it to track the asset creation, and we have created a primary key for the asset it self “bookmarkId” and then we have added something called finder, finder will be translated in the generated code as a query over bookmarks by the finder column which is in our case is the userId.
For the references, we are adding those references to integrate the bookmark entity with the asset framework.
Now we have defined our data structure we will need to build the service which will generate the local service and remote service.
The question is what is the difference between local service and remote service?
Local Service will be used without permission check, so the one who will be calling this service will be able to crawl all of the bookmarks with regards to the permissions, Remote Service will be calling the permission checker on each call.
Please follow the below steps in order to build your service:
Open Gradle Panel
Navigate to: Exercise-Project -> Modules -> services -> services-service -> tasks -> build
Run the “BuildService” task.
Once the build service successfully completed you will find the following under your module:
Figure ( 02 )
Gradle has successfully generated the source code for the bookmarks local and remote services along with the data model.
Now we need to create our own code to Create / Remove / Get bookmark(s), this will require for your side a knowledge of Java programming, Liferay does not impose a special programming language or something specialized and tailored only for Liferay.
Let's start by implementing the local service for the required methods:
Local Service Code
Create new bookmark method
public Bookmark addBookmark(long groupId,String title, String description, String url
, ServiceContext serviceContext) throws PortalException {
Group group = groupLocalService.getGroup(groupId);
long userId = serviceContext.getUserId();
User user = userLocalService.getUser(userId);
// Generate primary key for the bookmark.
long bookmarkId = counterLocalService.increment(Bookmark.class.getName());
Bookmark toBeCreated = createBookmark(bookmarkId);
toBeCreated.setGroupId(groupId);
toBeCreated.setUserId(userId);
toBeCreated.setUserName(user.getScreenName());
toBeCreated.setCreateDate(serviceContext.getCreateDate(new Date()));
toBeCreated.setModifiedDate(serviceContext.getModifiedDate(new Date()));
toBeCreated.setTitle(title);
toBeCreated.setDescription(description);
toBeCreated.setUrl(url);
return super.addBookmark(toBeCreated);
}
Delete bookmark method
Public void removeBookmarkById(long bookmarkId,ServiceContext serviceContext)
{
bookmarkPersistence.removeByBookmarkId(bookmarkId);
Get bookmarks method
public List<Bookmark> getBookmarksForCurrentUser(ServiceContext serviceContext)
List<Bookmark> result =
bookmarkPersistence.findByUserId(serviceContext.getUserId());
return result;
Remote Service Code
public Bookmark addBookmark(long groupId, String title, String description, String url
return bookmarkLocalService.addBookmark
(groupId,title,description,url,serviceContext);}
public void removeBookmarkById(long bookmarkId,ServiceContext serviceContext)
{bookmarkLocalService.removeBookmarkById(bookmarkId,serviceContext);}
{return bookmarkLocalService.getBookmarksForCurrentUser(serviceContext);}
With the above implemented, you have successfully implemented the OSGI service and the JSON Web Services, and that's the beauty of Liferay Developer tools.
Lets deploy it and discover the newly created JSON APIs, in order to build it, please follow the below steps:
Navigate to: Exercise-Project -> Tasks -> Docker
Run the “BuildDockerImage” task.
Navigate to your project folder and open the build -> Docker -> Deploy
Copy both *.Jar files to your liferay bundle -> Deploy folder
Navigate to your Liferay JSON APIs explorer.
http://<Liferay:Port>/api/jsonws
From the services list select bookmark
Figure ( 03 )
Inorder to build your user interface with Liferay, you have many options:
JSP Widget
Progressive Web Application
Angular
React
VueJS
JavaScript
For this blog, we will be working with Angular to build the user interface.
We will be creating a widget which will be adding the current url as a bookmark, for that please follow the below steps:
Navigate to the Workspace folder structure and select Modules.
Right click on Modules and click on Liferay Module.
Provide the package “com.liferay.training.bookmarks.web.create”.
Select “NPM-Angular-Portlet” for the module type and click next.
Provide the module name “addBookmark”
Click Finish.
Navigate to the app.component.ts inside Modules -> addBookmark -> src -> main -> resources -> META-INFO -> lib -> app
Place the following code
import { Component } from '@angular/core';
declare const Liferay: any;
@Component({
template: `<button (click)="addToBookmarks()">Add To Bookmarks</button>`,
})
export class AppComponent {
currentURL()
return Liferay.ThemeDisplay.getLayoutURL();
getRelativeURL()
return Liferay.ThemeDisplay.getLayoutRelativeURL();
generateServicePromise(serviceURL:string,serviceObject:any):Promise<any>
const prom = new Promise((resolve,reject)=>{
Liferay.Service(serviceURL,serviceObject,(result:any)=>{
resolve(result);
},(error:any)=>{
reject(error);
});
return prom;
getGroupId()
return Liferay.ThemeDisplay.getScopeGroupId();
async addToBookmarks()
var serviceUrl = "/bookmark.bookmark/add-bookmark";
var object = {
groupId:this.getGroupId() ,
title: this.getRelativeURL(),
description: '',
url: this.currentURL()
var prom = this.generateServicePromise(serviceUrl,object);
var toBeAdded = await prom;
Build your module and deploy the generated Jar file which will add a new widget to your page editor toolbox.
Figure ( 04 )
Provide the package “com.liferay.training.bookmarks.web.list”.
Provide the module name “listBookmark”
import {Component, OnInit} from '@angular/core';
template: `<div *ngFor="let bookmark of bookmarks" class="row">
<div class="col-10">
{{bookmark.title}}
</div>
<div class="col-2">
<button class="btn btn-danger" (click)="deleteBookmark(bookmark.bookmarkId)">
Delete
</button>
</div>`,
export class AppComponent implements OnInit{
public bookmarks:Array<any> = [];
async deleteBookmark(bookmarkId:number)
var serviceUrl = "/bookmark.bookmark/remove-bookmark-by-id";
var object =
bookmarkId:bookmarkId
};
var prom = this.generateServicePromise(serviceUrl, object);
await prom;
this.getBookmark();
async getBookmark()
var serviceUrl = "/bookmark.bookmark/get-bookmarks-for-current-user";
var prom = this.generateServicePromise(serviceUrl, {});
this.bookmarks = await prom;
ngOnInit(): void {
Please visit the following git repository to have a copy of the code we have been working on in this blog.
https://github.com/mahmoudhtayem87/LR-Bookmarks