Service Builder
Service
Builder is a model-driven code generation tool built by Liferay to
automate the creation of interfaces and classes for database persistence
and a service layer. Service Builder will generate most of the common
code needed to implement find, create, update, and delete operations on
the database, allowing you to focus on the higher level aspects of
service design.
The service layer generated by
Service Builder, has an implementation class that is responsible to
handle retrieving and storing data classes and adding the necessary
business logic around them. This layer can optionally be composed of
two layers, the local service and the remote service. The local service
contains the business logic and accesses the persistence layer. It can
be invoked by client code running in the same Java Virtual Machine.
The remote service usually ads a code to check security and is meant to
be accessible from anywhere over the Internet or your local network.
Service Builder automatically generates the code necessary to allow
access to the remote services using SOAP, JSON and Java RMI.
Define the Model
The
first step in using Service Builder is to define your model classes
and their attributes in a service.xml file. For convenience, we will
define the service within the my-greeting portlet, although it should
be placed inside a new portlet. Create a file named service.xml in
portlets/my-greeting-portlet/docroot/WEB-INF inside the Plugins SDK and
add the following content:
<?xml version="1.0"?>
<!DOCTYPE service-builder PUBLIC "-//Liferay//DTD Service Builder 6.0.0//EN" "http://www.liferay.com/dtd/liferay-service-builder_6_0_0.dtd">
<service-builder package-path="com.sample.portlet.library">
<namespace>Library</namespace>
<entity name="Book" local-service="true" remote-service="true">
<!-- PK fields -->
<column name="bookId" type="long" primary="true" />
<!-- 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" />
</entity>
</service-builder>
<!DOCTYPE service-builder PUBLIC "-//Liferay//DTD Service Builder 6.0.0//EN" "http://www.liferay.com/dtd/liferay-service-builder_6_0_0.dtd">
<service-builder package-path="com.sample.portlet.library">
<namespace>Library</namespace>
<entity name="Book" local-service="true" remote-service="true">
<!-- PK fields -->
<column name="bookId" type="long" primary="true" />
<!-- 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" />
</entity>
</service-builder>
Overview of service.xml
<service-builder package-path="com.sample.portlet.library">
This
specifies the package path that the class will generate to. In this
example, classes will generate to
WEB-INF/src/com/sample/portlet/library/
<namespace>Library</namespace>
The namespace element must be a unique namespace for this component. Table names will be prepended with this namepace.
<entity name="Book" local-service="true" remote-service="false">
The entity name is the database table you want to create.
<column name="title" type="String" />
Columns
specified in service.xml will be created in the database with a data
type appropriate to the Java type. Accessors will be automatically
generated for these attributes in the model class.
Tip:
Always consider adding two long fields called groupId and companyId to
your data models. These two fields will allow your portlet to support
the multi-tenancy features of Liferay so that each community or
organization (for each portal instance) can have its own independent
data.
Generate the Service
Open a terminal window in your portlets/my-greeting-portlet directory and enter this command:
ant build-service
The
service has been generated successfully when you see "BUILD
SUCCESSFUL." In the terminal window, you should see that a large number
of files have been generated. An overview of these files is provided
below:
- Persistence
- BookPersistence - book persistence interface @generated
- BookPersistenceImpl - book persistence @generated
- BookUtil - book persistence util, instances BookPersistenceImpl @generated
- Local Service
- BookLocalServiceImpl - local service implementation. This is the only class within the local service that you will be able to modify manually. Your business logic will be here.
- BookLocalService - local service interface @generated
- BookLocalServiceBaseImpl - local service base @generated @abstract
- BookLocalServiceUtil - local service util, instances BookLocalServiceImpl @generated
- BookLocalServiceWrapper - local service wrapper, wraps BookLocalServiceImpl @generated
- Remote Service
- BookServiceImpl - remove service implementation. Put here the code that adds additional security checks and invokes the local service.
- BookService - remote service interface @generated
- BookServiceBaseImpl - remote service base @generated @abstract
- BookServiceUtil - remote service util, instances BookServiceImpl @generated
- BookServiceWrapper - remote service wrapper, wraps BookServiceImpl @generated
- BookServiceSoap - soap remote service, proxies BookServiceUtil @generated
- BookSoap - soap book model, similar to BookModelImpl, does not implement Book @generated
- BookServiceHttp - http remote service, proxies BookServiceUtil @generated
- BookJSONSerializer - json serializer, converts Book to JSON array @generated
- Model
- BookModel - book base model interface @generated
- BookModelImpl - book base model @generated
- Book - book model interface @generated
- BookImpl - book model implementation. You can use this class to add additional methods to your model other than the autogenerated field getters and setters.
- BookWrapper - book wrapper, wraps Book @generated
Out of all of these classes only three can be manually modified: BookLocalServiceImpl, BookServiceImpl and BookImpl.
Write the Local Service Class
In
the file overview above, you will see that BookLocalService is the
interface for the local service. It contains the signatures of every
method in BookLocalServiceBaseImpl and BookLocalServiceImpl.
BookLocalServiceBaseImpl contains a few automatically generated methods
providing common functionality. Since this class is generated, you
should never modify it, or your changes will be overwritten the next
time you run Service Builder. Instead, all custom code should be placed
in BookLocalServiceImpl.
Open the following file:
/docroot/WEB-INF/src/com/sample/portlet/library/service/impl/BookLocalServiceImpl.java
We
will be adding the database interaction methods to this service layer
class. Add the following method to the BookLocalServiceImpl class:
public Book addBook(long userId, String title)
throws PortalException, SystemException {
User user = UserUtil.findByPrimaryKey(userId);
Date now = new Date();
long bookId = CounterLocalServiceUtil.increment(Book.class.getName());
Book book = bookPersistence.create(bookId);
book.setTitle(title);
book.setCompanyId(user.getCompanyId());
book.setUserId(user.getUserId());
book.setUserName(user.getFullName());
book.setCreateDate(now);
book.setModifiedDate(now);
book.setTitle(title);
return bookPersistence.update(book);
}
throws PortalException, SystemException {
User user = UserUtil.findByPrimaryKey(userId);
Date now = new Date();
long bookId = CounterLocalServiceUtil.increment(Book.class.getName());
Book book = bookPersistence.create(bookId);
book.setTitle(title);
book.setCompanyId(user.getCompanyId());
book.setUserId(user.getUserId());
book.setUserName(user.getFullName());
book.setCreateDate(now);
book.setModifiedDate(now);
book.setTitle(title);
return bookPersistence.update(book);
}
Before you can use this new method,
you must add its signature to the BookLocalService interface by running
service builder again.
Navigate to the root folder of your portlet in the terminal and run:
ant build-service
Service
Builder looks through BookLocalServiceImpl and automatically copies
the signatures of each method into the interface. You can now add a new
book to the database by making the following call
BookLocalServiceUtil.addBook(userId, "A new title");
Built-In Liferay Services
In
addition to the services you create using Service Builder, your
portlets may also access a variety of services built into Liferay. These
include UserService, OrganizationService, GroupService,
CompanyService, ImageService, LayoutService, OrganizationService,
PermissionService, UserGroupService, and RoleService. For more
information on these services, see Liferay in Action and Liferay's
Javadocs.
Security and Permissions Service
Liferay
Portal implements a fine-grained permissions system, which developers
can use to implement access security into their custom portlets, giving
administrators and users a lot more control over their portlets and
content. This section of the document will provide a reference for
implementing security into custom portlets.
- Overview
Adding fine-grained permissions to custom portlets consists of four main steps (also known as DRAC):
- Define all resources and their permissions.
- Register all the resources defined in step 1 into the permissions system. This is also known simply as "adding resources."
- Associate the necessary permissions to these resources.
- Check permission before returning resources.
- Implementing Permissions
In this section, each of the four main steps in
adding Liferay's security feature into custom portlets (built on top of
the Liferay portal) will be explained. The following are two
definitions that are important to remember.
Resource
- A generic term for any object represented in the portal. Examples of
resources include portlets (e.g., Message Boards, Calendar, etc.),
Java classes (e.g., Message Board Topics, Calendar Events, etc.), and
files (e.g., documents, images, etc.)
Permission -
An action acting on a resource. For example, the view in "viewing the
calendar portlet" is defined as a permission in Liferay.
Keep
in mind that the permission for a portlet resource is implemented a
little differently from the other resources such as Java classes and
files. In each of the subsections below, the permission implementation
for the portlet resource is explained first, then the model (and file)
resource.
For your custom portlet,
Liferay portal needs to know whether there are resources that require
permission and whether there are custom permissions. The default
configuration is encapsulated in an XML file found in the portal source
under the /portal-impl/classes/resource-actions directory; you might
use it as a reference to create a similar file for your portlet. There
is also a Sample Permissions portlet available in the Plugins project
on SourceForge. If your portlet only needs the view and the
configuration permission, and that the portlet doesn't use any models
with permission, then you do not need to create this XML file. The
reason is that all portlets in Liferay automatically inherit these
permissions. However, if your portlet does have custom permission
and/or uses models that have custom permissions, then you will need to
create an XML file defining the resources and actions. Let's take a
look at blogs.xml in portal/portal-impl/classes/resource-actions and
see how the blogs portlet defined these resources and actions:
<?xml version="1.0"?>
<resource-action-mapping>
<portlet-resource>
<portlet-name>33</portlet-name>
<supports>
<action-key>ADD_ENTRY</action-key>
<action-key>CONFIGURATION</action-key>
<action-key>VIEW</action-key>
</supports>
<community-defaults>
<action-key>VIEW</action-key>
</community-defaults>
<guest-defaults>
<action-key>VIEW</action-key>
</guest-defaults>
<guest-unsupported>
<action-key>ADD_ENTRY</action-key>
</guest-unsupported>
</portlet-resource>
<model-resource>
<model-name>com.liferay.portlet.blogs.model.BlogsEntry</model-name>
<portlet-ref>
<portlet-name>33</portlet-name>
</portlet-ref>
<supports>
<action-key>ADD_DISCUSSION</action-key>
<action-key>DELETE</action-key>
<action-key>DELETE_DISCUSSION</action-key>
<action-key>PERMISSIONS</action-key>
<action-key>UPDATE</action-key>
<action-key>UPDATE_DISCUSSION</action-key>
<action-key>VIEW</action-key>
</supports>
<community-defaults>
<action-key>ADD_DISCUSSION</action-key>
<action-key>VIEW</action-key>
</community-defaults>
<guest-defaults>
<action-key>VIEW</action-key>
</guest-defaults>
<guest-unsupported>
<action-key>ADD_DISCUSSION</action-key>
<action-key>DELETE</action-key>
<action-key>DELETE_DISCUSSION</action-key>
<action-key>PERMISSIONS</action-key>
<action-key>UPDATE</action-key>
<action-key>UPDATE_DISCUSSION</action-key>
</guest-unsupported>
</model-resource>
</resource-action-mapping>
<resource-action-mapping>
<portlet-resource>
<portlet-name>33</portlet-name>
<supports>
<action-key>ADD_ENTRY</action-key>
<action-key>CONFIGURATION</action-key>
<action-key>VIEW</action-key>
</supports>
<community-defaults>
<action-key>VIEW</action-key>
</community-defaults>
<guest-defaults>
<action-key>VIEW</action-key>
</guest-defaults>
<guest-unsupported>
<action-key>ADD_ENTRY</action-key>
</guest-unsupported>
</portlet-resource>
<model-resource>
<model-name>com.liferay.portlet.blogs.model.BlogsEntry</model-name>
<portlet-ref>
<portlet-name>33</portlet-name>
</portlet-ref>
<supports>
<action-key>ADD_DISCUSSION</action-key>
<action-key>DELETE</action-key>
<action-key>DELETE_DISCUSSION</action-key>
<action-key>PERMISSIONS</action-key>
<action-key>UPDATE</action-key>
<action-key>UPDATE_DISCUSSION</action-key>
<action-key>VIEW</action-key>
</supports>
<community-defaults>
<action-key>ADD_DISCUSSION</action-key>
<action-key>VIEW</action-key>
</community-defaults>
<guest-defaults>
<action-key>VIEW</action-key>
</guest-defaults>
<guest-unsupported>
<action-key>ADD_DISCUSSION</action-key>
<action-key>DELETE</action-key>
<action-key>DELETE_DISCUSSION</action-key>
<action-key>PERMISSIONS</action-key>
<action-key>UPDATE</action-key>
<action-key>UPDATE_DISCUSSION</action-key>
</guest-unsupported>
</model-resource>
</resource-action-mapping>
In the
XML, the first thing defined is the portlet itself. Right under the
root element <resource-action-mapping>, we have a child element
called <portlet-resource>. In this element, we define the portlet
name, which is 33 in our case. Next, we list all the actions this
portlet supports under the <supports> tag. Keep in mind that this
is at the portlet level. To understand what should be listed here,
developers should ask themselves what actions belong to the portlet
itself or what actions are performed on the portlet that may require a
security check. In our case, users need permission to add an entry
(ADD_ENTRY), configure blogs portlet settings (CONFIGURATION), and view
the blogs itself (VIEW). Each of these supported permissions is within
its own <action-key> tag. After we've defined all the actions
that require a check, we move on to define some of the default
permission settings. The community-defaults tag defines what actions
are permitted by default for this portlet on the community (group) page
upon which the portlet resides. To put it another way, what should a
user that has access to the community in which this portlet resides be
able to do minimally? For the blogs portlet, a user with access to the
community containing the blogs portlet should be able to view it.
Likewise, the guest-defaults tag defines what actions are permitted by
default to guests visiting a layout containing this portlet. So if a
guest has access to the community page that contains a blogs portlet,
the guest should, at the very least, be able to view the portlet
according to blogs.xml (not necessarily the content of the portlet).
Otherwise, the guest will see an error message within the portlet.
Depending on your custom portlet, you may add more actions here that
make sense. The guest-unsupported tag contains actions that a visiting
guest should never be able to do. For example, the guest visiting the
blogs portlet should never be able to add a blog entry since the blog
belongs to either a user or a group of users. So even if a user wants
to grant guests the ability to add a blog entry to her blog, there is
no way for her to grant that permission because the blogs.xml doesn't
permit such an action for guests.
After
defining the portlet as a resource, we move on to define models within
the portlet that also require access check. The model resource is
surrounded by the <model-resource> tag. Within this tag, we first
define the model name. This must be the fully qualified Java class
name of the model. Next we define the portlet name that this model
belongs to under the portlet-ref tag. Though unlikely, a model can
belong to multiple portlets, which you may use multiple
<portlet-name> tags to define. Similar to the portlet resource
element, the model resource element also allows you to define a
supported list of actions that require permission to perform. You must
list out all the performable actions that require a permission check.
As you can see for a blog entry, a user must have permission in order
to add comments to an entry, delete an entry, change the permission
setting of an entry, update an entry, or simply to view an entry. The
<community-defaults> tag, the <guest-defaults> tag, and the
<guest-unsupported> tag are all similar in meaning to what's
explained above for a portlet resource.
After
defining your permission scheme for your custom portlet, you then need
to tell Liferay the location of this file. For Liferay core, the XML
file would normally reside in
portal/portal-impl/classes/resource-actions and a reference to the file
would appear in the default.xml file. For a plugin, you should put the
file in a directory that is in the class path for the project. Then
create a properties file for your portlet (the one in the Sample
Permissions Portlet is simply called,
sample-permissions-portlet.properties) and create a property called
resource.actions.configs with a value that points to the the XML file.
Below is an example from the Sample Permissions Portlet:
resource.actions.configs=resource-actions/sample-permissions-portlet.xml
- Adding Resource
After defining resources and actions,
the next task is to write code that adds resources into the permissions
system. A lot of the logic to add resources is encapsulated in the
ResourceLocalServiceImpl class. So adding resources is as easy as
calling the add resource method in ResourceLocalServiceUtil class.
public void addResources(
String companyId, String groupId, String userId, String name,
String primKey, boolean portletActions,
boolean addCommunityPermissions, boolean addGuestPermissions);
String companyId, String groupId, String userId, String name,
String primKey, boolean portletActions,
boolean addCommunityPermissions, boolean addGuestPermissions);
For
all the Java objects that require access permission, you need to make
sure that they are added as resources every time a new one is created.
For example, every time a user adds a new entry to her blog, the
addResources(…) method is called to add the new entry to the resource
system. Here's an example of the call from the
BlogsEntryLocalServiceImpl class.
ResourceLocalServiceUtil.addResources(
entry.getCompanyId(), entry.getGroupId(), entry.getUserId(),
BlogsEntry.class.getName(), entry.getPrimaryKey().toString(),
false, addCommunityPermissions, addGuestPermissions);
entry.getCompanyId(), entry.getGroupId(), entry.getUserId(),
BlogsEntry.class.getName(), entry.getPrimaryKey().toString(),
false, addCommunityPermissions, addGuestPermissions);
The
parameters companyId, groupId, and userId should be self explanatory.
The name parameter is the fully qualified Java class name for the
resource object being added. The primKey parameter is the primary key of
the resource object. As for the portletActions parameter, set this to
true if you're adding portlet action permissions. In our example, we
set it to false because we're adding a model resource, which should be
associated with permissions related to the model action defined in
blogs.xml. The addCommunityPermissions and the addGuestPermissions
parameters are inputs from the user. If set to true,
ResourceLocalService will then add the default permissions to the
current community group and the guest group for this resource
respectively.
If you would like to
provide your user the ability to choose whether to add the default
community permission and the guest permission for the resources within
your custom portlet, Liferay has a custom JSP tag you may use to
quickly add that functionality. Simply insert the
<liferay-ui:input-permissions /> tag into the appropriate JSP and
the checkboxes will show up on your JSP. Of course, make sure the tag
is within the appropriate <form> tags.
To
prevent having a lot of dead resources taking up space in the
Resource_ database table, you must remember to remove them from the
Resource_ table when the resource is no longer applicable. Simply call
the deleteResource(…) method in ResourceLocalServiceUtil. Here's an
example of a blogs entry being removed:
ResourceLocalServiceUtil.deleteResource(
entry.getCompanyId(), BlogsEntry.class.getName(),
Resource.TYPE_CLASS, Resource.SCOPE_INDIVIDUAL,
entry.getPrimaryKey().toString());
entry.getCompanyId(), BlogsEntry.class.getName(),
Resource.TYPE_CLASS, Resource.SCOPE_INDIVIDUAL,
entry.getPrimaryKey().toString());
- Adding Permission
On the portlet level, no code needs to
be written in order to have the permission system work for your custom
portlet. Your custom portlet will automatically have all the permission
features. If you've defined any custom permissions (supported actions)
in your portlet-resource tag in section 3.1, those are automatically
added to a list of permissions and users can readily choose them. Of
course, for your custom permissions to have any value, you'll need to
show or hide certain functionality in your portlet. You can do that by
checking the permission first before performing the intended
functionality.
In order to allow a user
to set permissions on the model resources, you will need to expose the
permission interface to the user. This can be done by adding two
Liferay UI tags to your JSP. The first one is the
<liferay-security:permissionsURL> tag which returns a URL that
takes the user to the page to configure the permission settings. The
second tag is the <liferay-ui:icon> tag that shows a permission
icon to the user. Below is an example found in the file
view_entry_content.jspf.
<liferay-security:permissionsURL
modelResource="<%= BlogsEntry.class.getName() %>"
modelResourceDescription="<%= entry.getTitle() %>"
resourcePrimKey="<%= entry.getPrimaryKey().toString() %>"
var="entryURL"
/>
<liferay-ui:icon image="permissions" url="<%= entryURL %>" />
modelResource="<%= BlogsEntry.class.getName() %>"
modelResourceDescription="<%= entry.getTitle() %>"
resourcePrimKey="<%= entry.getPrimaryKey().toString() %>"
var="entryURL"
/>
<liferay-ui:icon image="permissions" url="<%= entryURL %>" />
The
attributes you need to provide to the first tag are modelResource,
modelResourceDescription, resourcePrimKey, and var. The modelResource
attribute is the fully qualified Java object class name. It then gets
translated in Language.properties to a more readable name.
As
for the modelResourceDescription attribute, you can pass in anything
that best describes this model instance. In the example, the blogs title
was passed in. The resourcePrimKey attribute is simply the primary key
of your model instance. The var attribute is the variable name this
URL String will get assigned to. This variable is then passed to the
<liferay-ui:icon> tag so the permission icon will have the proper
URL link. There's also an optional attribute redirect that's available
if you want to override the default behavior of the upper right arrow
link. That is all you need to do to enable users to configure the
permission settings for model resources.
- Checking Permissions
The last major step to implementing permission to
your custom portlet is to check permission. This may be done in a
couple of places. For example, your business layer should check for
permission before deleting a resource, or your user interface should
hide a button that adds a model (e.g., a calendar event) if the user
does not have permission to do so.
Similar to the
other steps, the default permissions for the portlet resources are
automatically checked for you. You do not need to implement anything
for your portlet to discriminate whether a user is allowed to view or
to configure the portlet itself. However, you do need to implement any
custom permission you have defined in your resource-actions XML file. In
the blogs portlet example, one custom supported action is ADD_ENTRY.
There are two places in the source code that check for this permission.
The first one is in the file view_entries.jsp. The presence of the add
entry button is contingent on whether the user has permission to add
entry (and also whether the user is in tab one).
<%
boolean showAddEntryButton = tabs1.equals("entries") && PortletPermission.contains(permissionChecker, plid, PortletKeys.BLOGS, ActionKeys.ADD_ENTRY);
%>
boolean showAddEntryButton = tabs1.equals("entries") && PortletPermission.contains(permissionChecker, plid, PortletKeys.BLOGS, ActionKeys.ADD_ENTRY);
%>
The second place that checks for the add
entry permission is in the file BlogsEntryServiceImpl. (Notice the
difference between this file and the BlogsEntryLocalServiceImpl.) In
the addEntry(…) method, a call is made to check whether the incoming
request has permission to add entry.
PortletPermission.check(
getPermissionChecker(), plid, PortletKeys.BLOGS,
ActionKeys.ADD_ENTRY);
getPermissionChecker(), plid, PortletKeys.BLOGS,
ActionKeys.ADD_ENTRY);
If the check fails, it
throws a PrincipalException and the add entry request aborts. You're
probably wondering what the PortletPermission and the PermissionChecker
classes do. Let's take a look at these two classes.
The
PermissionChecker class has a method called hasPermission(…) that
checks whether a user making a resource request has the necessary
access permission. If the user is not signed in (guest user), it checks
for guest permissions. Otherwise, it checks for user permissions. This
class is available to you in two places. First in your business logic
layer, you can obtain an instance of the PermissionChecker by calling
the getPermissionChecker() method inside your ServiceImpl class. This
method is available because all ServiceImpl (not LocalServiceImpl)
classes extend the PrincipalBean class, which implements the
getPermissionChecker() method. The other place where you can obtain an
instance of the PermissionChecker class is in your JSP files. If your
JSP file contains the portlet tag <portlet:defineObjects /> or
includes another JSP file that does, you'll have an instance of the
PermissionChecker class available to you via the permissionChecker
variable. Now that you know what the PermissionChecker does and how to
obtain an instance of it, let's take a look at Liferay's convention in
using it.
PortletPermission is a helper class
that makes it easy for you to check permission on portlet resources (as
opposed to model resources, covered later). It has two static methods
called check(…) and another two called contains(…). They are all
essentially the same. The two differences between them are:
- One check(…) method and one contains(…) method take in the portlet layout ID variable (plid).
- The check(…) methods throw a new PrincipalException if user does not have permission, and the contains(…) methods return a boolean indicating whether user has permission.
The contains(…) methods are meant to be used in
your JSP files since they return a boolean instead of throwing an
exception. The check(…) methods are meant to be called in your business
layer (ServiceImpl). Let's revisit the blogs portlet example below.
(The addEntry(…) method is found in BlogsEntryServiceImpl.)
public BlogsEntry addEntry(
long plid, String title, String content, int displayDateMonth,
int displayDateDay, int displayDateYear, int displayDateHour,
int displayDateMinute, String[] tagsEntries,
boolean addCommunityPermissions, boolean addGuestPermissions,
ThemeDisplay themeDisplay)
throws PortalException, SystemException {
PortletPermissionUtil.check(
getPermissionChecker(), plid, PortletKeys.BLOGS,
ActionKeys.ADD_ENTRY);
return blogsEntryLocalService.addEntry(
getUserId(), plid, title, content, displayDateMonth, displayDateDay,
displayDateYear, displayDateHour, displayDateMinute, tagsEntries,
addCommunityPermissions, addGuestPermissions, themeDisplay);
}
long plid, String title, String content, int displayDateMonth,
int displayDateDay, int displayDateYear, int displayDateHour,
int displayDateMinute, String[] tagsEntries,
boolean addCommunityPermissions, boolean addGuestPermissions,
ThemeDisplay themeDisplay)
throws PortalException, SystemException {
PortletPermissionUtil.check(
getPermissionChecker(), plid, PortletKeys.BLOGS,
ActionKeys.ADD_ENTRY);
return blogsEntryLocalService.addEntry(
getUserId(), plid, title, content, displayDateMonth, displayDateDay,
displayDateYear, displayDateHour, displayDateMinute, tagsEntries,
addCommunityPermissions, addGuestPermissions, themeDisplay);
}
Before the addEntry(…) method calls
BlogsEntryLocalServiceUtil.addEntry(…) to add a blogs entry, it calls
PortletPermission.check(…) to validate user permission. If the check
fails, a PrincipalException is thrown and an entry will not be added.
Note the parameters passed into the method. Again, the
getPermissionChecker() method is readily available in all ServiceImpl
classes. The plid variable is passed into the method by its caller
(most likely from a PortletAction class). PortletKeys.BLOGS is just a
static String indicating that the permission check is against the blogs
portlet. ActionKeys.ADD_ENTRY is also a static String to indicate the
action requiring the permission check. You're encouraged to do likewise
with your custom portlet names and custom action keys.
Whether
you need to pass in a portlet layout ID (plid) depends on whether your
custom portlet supports multiple instances. Let's take a look at the
message board portlet for example. A community may need three separate
page layouts, each having a separate instance of the message board
portlet. Only by using the portlet layout ID will the permission system
be able to distinguish the three separate instances of the message
board portlet. This way, permission can be assigned separately in all
three instances. Though in general, most portlets won't need to use the
portlet layout ID in relation to the permission system.
Since
the ServiceImpl class extends the PrincipalBean class, it has access
to information of the current user making the service request.
Therefore, the ServiceImpl class is the ideal place in your business
layer to check user permission. Liferay's convention is to implement
the actual business logic inside the LocalServiceImpl methods, and then
the ServiceImpl calls these methods via the LocalServiceUtil class
after the permission check completes successfully. Your PortletAction
classes should make calls to ServiceUtil (wrapper to ServiceImpl)
guaranteeing that permission is first checked before the request is
fulfilled.
Checking model resource permission is
very similar to checking portlet resource permission. The only major
difference is that instead of calling methods found in the
PortletPermission class mentioned previously, you need to create your
own helper class to assist you in checking permission. The next section
will detail how this is done.
It is advisable to
have a helper class to help check permission on your custom models.
This custom permission class is similar to the PortletPermission class
but is tailored to work with your custom models. While you can
implement this class however you like, we encourage you to model your
implementation after the PortletPermission class, which contains four
static methods. Let's take a look at the BlogsEntryPermission class.
public class BlogsEntryPermission {
public static void check(
PermissionChecker permissionChecker, long entryId, String actionId)
throws PortalException, SystemException {
if (!contains(permissionChecker, entryId, actionId)) {
throw new PrincipalException();
}
}
public static void check(
PermissionChecker permissionChecker, BlogsEntry entry,
String actionId)
throws PortalException, SystemException {
if (!contains(permissionChecker, entry, actionId)) {
throw new PrincipalException();
}
}
public static boolean contains(
PermissionChecker permissionChecker, long entryId, String actionId)
throws PortalException, SystemException {
BlogsEntry entry = BlogsEntryLocalServiceUtil.getEntry(entryId);
return contains(permissionChecker, entry, actionId);
}
public static boolean contains(
PermissionChecker permissionChecker, BlogsEntry entry,
String actionId)
throws PortalException, SystemException {
return permissionChecker.hasPermission(
entry.getGroupId(), BlogsEntry.class.getName(), entry.getEntryId(),
actionId);
}
}
public static void check(
PermissionChecker permissionChecker, long entryId, String actionId)
throws PortalException, SystemException {
if (!contains(permissionChecker, entryId, actionId)) {
throw new PrincipalException();
}
}
public static void check(
PermissionChecker permissionChecker, BlogsEntry entry,
String actionId)
throws PortalException, SystemException {
if (!contains(permissionChecker, entry, actionId)) {
throw new PrincipalException();
}
}
public static boolean contains(
PermissionChecker permissionChecker, long entryId, String actionId)
throws PortalException, SystemException {
BlogsEntry entry = BlogsEntryLocalServiceUtil.getEntry(entryId);
return contains(permissionChecker, entry, actionId);
}
public static boolean contains(
PermissionChecker permissionChecker, BlogsEntry entry,
String actionId)
throws PortalException, SystemException {
return permissionChecker.hasPermission(
entry.getGroupId(), BlogsEntry.class.getName(), entry.getEntryId(),
actionId);
}
}
Again, the two check(…) methods are meant to be
called in your business layer, while the two contains(…) methods can
be used in your JSP files. As you can see, it's very similar to the
PortletPermission class. The two notable differences are:
- Instead of having the portletId as one of the parameters, the methods in this custom class take in either an entryId or a BlogsEntry object.
- None of the methods need to receive the portlet layout ID (plid) as a parameter. (Your custom portlet may choose to use the portlet layout ID if need be.)
Let's see how this class is used in the blogs portlet code.
public BlogsEntry getEntry(String entryId) throws PortalException, SystemException {
BlogsEntryPermission.check(
getPermissionChecker(), entryId, ActionKeys.VIEW);
return BlogsEntryLocalServiceUtil.getEntry(entryId);
}
BlogsEntryPermission.check(
getPermissionChecker(), entryId, ActionKeys.VIEW);
return BlogsEntryLocalServiceUtil.getEntry(entryId);
}
In the BlogsEntryServiceImpl class is a method
called getEntry(…). Before this method returns the blogs entry object,
it calls the custom permission helper class to check permission. If
this call doesn't throw an exception, the entry is retrieved and
returned to its caller.
<c:if test="<%= BlogsEntryPermission.contains(permissionChecker, entry, ActionKeys.UPDATE) %>">
<portlet:renderURL windowState="<%= WindowState.MAXIMIZED.toString() %>" var="entryURL">
<portlet:param name="struts_action" value="/blogs/edit_entry" />
<portlet:param name="redirect" value="<%= currentURL %>" />
<portlet:param name="entryId" value="<%= entry.getEntryId() %>" />
</portlet:renderURL>
<liferay-ui:icon image="edit" url="<%= entryURL %>" />
</c:if>
<portlet:renderURL windowState="<%= WindowState.MAXIMIZED.toString() %>" var="entryURL">
<portlet:param name="struts_action" value="/blogs/edit_entry" />
<portlet:param name="redirect" value="<%= currentURL %>" />
<portlet:param name="entryId" value="<%= entry.getEntryId() %>" />
</portlet:renderURL>
<liferay-ui:icon image="edit" url="<%= entryURL %>" />
</c:if>
In the view_entry_content.jsp file,
the BlogsEntryPermission.contains(…) method is called to check whether
or not to show the edit button. That's all there is to it!
Let's
review what we've just covered. Implementing permission into your
custom portlet consists of four main steps. First step is to define any
custom resources and actions. Next step is to implement code to
register (or add) any newly created resources such as a BlogsEntry
object. The third step is to provide an interface for the user to
configure permission. Lastly, implement code to check permission before
returning resources or showing custom features. Two major resources
are portlets and Java objects. There is not a lot that needs to be done
for the portlet resource to implement the permission system since
Liferay Portal has a lot of that work done for you. You mainly focus
your efforts on any custom Java objects you've built. You're now well
on your way to implement security to your custom Liferay portlets!
Excellent informative post.. Real Estate In Bhopal
ReplyDeleteFirst of all web developers should be understand the exact requirements and concept of website creation from the consumers.It would help to neglect some technical issues during project time.
ReplyDeleteWeb Designing Companies | Web Design Companies
search the Best Commercial Property For Buy
ReplyDeleteFor other topics on liferay i found this very helpful Liferay Tutorials
ReplyDelete• Nice and good article. It is very useful for me to learn and understand easily. Thanks for sharing your valuable information and time. Please keep updatingAzure Online course Hyderabad
ReplyDeleteThanks for the post, I am techno savvy. I believe you hit the nail right on the head. I am highly impressed with your blog.
ReplyDeleteIt is very nicely explained. Your article adds best knowledge to our Java Online Training from India.
or learn thru Java Online Training from India Students.
A lot of thanks for all of the hard work on this web page. Betty delights in setting aside time for research and it's really easy to see why. My spouse and i learn all of the dynamic way you produce very helpful techniques through your website and attract contribution from other ones about this situation while our daughter is without a doubt starting to learn a great deal. Take advantage of the remaining portion of the new year. You are conducting a stunning job. remote service Virginia
ReplyDeleteThanks for the nice blog. It was very useful for me. I'm happy I found this blog. Thank you for sharing with us,I too always learn something new from your post. spicewood custom pool builder
ReplyDeleteI have read your article, it is very informative and helpful for me.I admire the valuable information you offer in your articles. Thanks for posting it.. spicewood custom pool builder
ReplyDeleteThanks for sharing your informative blog of post.Keep on sharing your ideas.
ReplyDeleteMicrosoft Windows Azure Training | Online Course | Certification in chennai | Microsoft Windows Azure Training | Online Course | Certification in bangalore | Microsoft Windows Azure Training | Online Course | Certification in hyderabad | Microsoft Windows Azure Training | Online Course | Certification in pune