Creating Remote Services
Step 1 of 1
Earlier, you used Service Builder to generate the Guestbook’s model, persistence, and service layers. Services generated by Service Builder can come in two flavors: local and remote. The local services you already used can only be invoked locally from the same OSGi container. Remote services can be invoked by any application with permission to access your server via the web. Remote services are published as JSON or SOAP.
For more information, click here to see the Service Builder Web Services section of tutorials.
Creating web services for the Guestbook application takes two steps:
-
Generate the web services with Service Builder.
-
Expose the services you want and wrap them in permission checks.
There’s a level of security in the assumption that local services can only be called by other services in the container. For example, the web app does permission and validation checks before calling services. To access these services, developers must be able to deploy their modules on the server. You don’t have these assurances when you expose services to the web, so you must check for permission before calling a service.
But first you must tell Service Builder to generate web services. Follow these steps:
-
Open
service.xml
from theguestbook-service
module. Find the tags for theGuestbook
andEntry
entities:<entity name="Guestbook" local-service="true" uuid="true"> <entity name="Entry" local-service="true" uuid="true">
-
As described in the
service.xml
DTD,local-service
defaults tofalse
andremote-service
defaults totrue
. It helps other developers who read your code to specify what services are generated. Therefore, addremote-service="true"
to the entity tags of theGuestbook
andEntry
entities:<entity name="Guestbook" local-service="true" remote-service="true" uuid="true"> <entity name="Entry" local-service="true" remote-service="true" uuid="true">
-
In the Gradle Tasks window on the right-hand side of Liferay Developer Studio, expand the service module’s build folder. Run Service Builder by double-clicking buildService. When Service Builder finishes, refresh the
guestbook-api
andguestbook-service
modules in the Project Explorer.
You may be interested to know that Service Builder did absolutely nothing. Since remote services are generated by default, you’ve always had their stubs in your project. All you did was make it explicit in the code.
By implementing local services, you separated concerns. Local services can assume things like permission checks have already been done before they’re called. This separates the business logic from the permissions logic. If instead you implemented everything in the remote services, this separation wouldn’t exist.
You may see code from other developers, however, who didn’t implement the local service, and instead elected to place all their business and permission logic in the remote service. This works, but makes the code less readable. With the concerns separated, a business logic bug is contained in the local service, and a permissions bug is contained in the remote service.
To expose remote services, you’ll implement methods in the -ServiceImpl
classes instead of the -LocalServiceImpl
classes. Since the primary concern is
permissions, however, first create a helper class to hold the permissions:
-
In the
src/main/java
folder, create the new packagecom.liferay.docs.guestbook.util
. In this new package, create thisActionKeys
class:package com.liferay.docs.guestbook.util; public class ActionKeys extends com.liferay.portal.kernel.security.permission.ActionKeys { public static final String ADD_ENTRY = "ADD_ENTRY"; public static final String ADD_GUESTBOOK = "ADD_GUESTBOOK"; }
The
ADD_ENTRY
andADD_GUESTBOOK
strings reference the permissions defined in theguestbook-service
module’sdocroot/WEB-INF/src/resource-actions/default.xml
file earlier in this Learning Path. It’s a best practice to create strings to refer to permissions in a class calledActionKeys
that extendscom.liferay.portal.kernel.security.permission.ActionKeys
. The parentActionKeys
contains strings that are used to refer to portal permissions. These include strings for common permissions such asVIEW
,UPDATE
,DELETE
, and so on. -
Add the following methods to the
GuestbookServiceImpl
class; then organize the imports by selecting Source → Organize Imports:public Guestbook addGuestbook(long userId, String name, ServiceContext serviceContext) throws SystemException, PortalException { return guestbookLocalService.addGuestbook(userId, name, serviceContext); } public Guestbook deleteGuestbook(long guestbookId, ServiceContext serviceContext) throws PortalException, SystemException { return guestbookLocalService.deleteGuestbook(guestbookId, serviceContext); } public List<Guestbook> getGuestbooks(long groupId) throws SystemException { return guestbookLocalService.getGuestbooks(groupId); } public List<Guestbook> getGuestbooks(long groupId, int start, int end) throws SystemException { return guestbookLocalService.getGuestbooks(groupId, start, end); } public int getGuestbooksCount(long groupId) throws SystemException { return guestbookLocalService.getGuestbooksCount(); } public Guestbook updateGuestbook(long userId, long guestbookId, String name, ServiceContext serviceContext) throws PortalException, SystemException { return guestbookLocalService.updateGuestbook(userId, guestbookId, name, serviceContext); }
These are stub remote service methods that expose each guestbook local service method. For now, the remote service method implementations call the local service implementations. Later, you’ll add permission checks to these methods, to wrap them in the same permissions you created in the UI. Service calls have no UI, so you must check for permission to access them. For now, you’re exposing the services to confirm they work and are accessible.
-
Add the following methods to the
EntryServiceImpl
class, then organize the imports as you did in step 2:public Entry addEntry(long userId, long guestbookId, String name, String email, String message, ServiceContext serviceContext) throws PortalException, SystemException { return entryLocalService.addEntry(userId, guestbookId, name, email, message, serviceContext); } public Entry deleteEntry(long entryId, ServiceContext serviceContext) throws PortalException, SystemException { return entryLocalService.deleteEntry(entryId, serviceContext); } public List<Entry> getEntries(long groupId, long guestbookId) throws SystemException { return entryLocalService.getEntries(groupId, guestbookId); } public List<Entry> getEntries(long groupId, long guestbookId, int start, int end) throws SystemException { return entryLocalService.getEntries(groupId, guestbookId, start, end); } public int getEntriesCount(long groupId, long guestbookId) throws SystemException { return entryLocalService.getEntriesCount(groupId, guestbookId); } public Entry updateEntry(long userId, long guestbookId, long entryId, String name, String email, String message, ServiceContext serviceContext) throws PortalException, SystemException { return entryLocalService.updateEntry(userId, guestbookId, entryId, name, email, message, serviceContext); }
Like you did for guestbooks, you’ve now created method stubs for guestbook entries. Each method implemented here exposes a service to the web. You’ll add permission checks in the next section.
-
Run Service Builder and refresh the API and service modules. Then redeploy the
guestbook-*
modules.
First, make sure you’re logged in as a user that can read guestbooks. Navigate
to Liferay DXP’s JSONWS page (http://[host name]:[port number]/api/jsonws
) and click the Context Name selector. The Guestbook app’s
context, gb
, appears as an option. Select it and confirm that your remote
service methods appear in the list.
Figure 1: After you've added remote service methods to your project's `*ServiceImpl` classes, run Service Builder and redeploy your modules. Then check that your remote services are accessible.
To test that your remote services work, choose a method to invoke. Pick a simple
method that doesn’t require a Service Context parameter, like
getGuestbooksCount(long groupId)
. To find the appropriate groupId
(the ID of
the site containing the Guestbook app), navigate to that site in your browser
and select Configuration → Site Settings from the Site Menu on the
left. The site ID is listed at the top of the Site Settings page. Now return to
the JSONWS page and enter the site ID into the group ID field and click
Invoke. Confirm that the correct number of guestbooks is returned. Great! Your
remote services work.
Next, you’ll build a WSDD (Web Service Deployment Descriptor) document for your remote services to make them available via SOAP (Simple Object Access Protocol).
Follow these steps to do so:
-
In your Liferay workspace’s
settings.gradle
file, add imports forServiceBuilderPlugin
andWSDDBuilderPlugin
before the buildscript block. Then add thegradle.beforeProject
closure at the bottom of the file:import com.liferay.gradle.plugins.service.builder.ServiceBuilderPlugin import com.liferay.gradle.plugins.wsdd.builder.WSDDBuilderPlugin ... gradle.beforeProject { project -> project.plugins.withType(ServiceBuilderPlugin) { project.apply plugin: WSDDBuilderPlugin } }
Refresh your workspace’s Gradle files: right click
settings.gradle
in the Project Explorer and select Gradle → Refresh Gradle Project. -
In the Gradle Tasks window on the right-hand side of Liferay Developer Studio, expand the service module’s build folder. Build the WSDD by double-clicking buildWSDD. If
buildWSDD
is missing, shut down your server and then restart Liferay Developer Studio. ThebuildWSDD
command appears as described.The WSDD builder generates a WSDD JAR file in the
guestbook-service
module’sbuild/libs
folder. Because this folder isn’t visible in Developer Studio, you must access it from the file system. The project’s modules are in the Eclipse workspace on the file system. Here’s the full file path to the WSDD JAR in your Eclipse workspace:com-liferay-docs-guestbook/modules/guestbook/guestbook-service/build/libs/com.liferay.docs.guestbook.service-wsdd-1.0.0.jar
If this file is missing, run
buildWSDD
again to generate it. -
Deploy the WSDD JAR file to Liferay DXP, which is in the Liferay Workspace’s
bundles
folder. To do this, copy and paste the WSDD JAR file into this folder in your Eclipse workspace on your file system:com-liferay-docs-guestbook/bundles/deploy
Return to Liferay Developer Studio and check the console to make sure deployment completes successfully.
-
Go to
http://[host name]:[port number]/o/com.liferay.docs.guestbook.service/api/axis
in your browser to view the Guestbook app’s SOAP web services. If you’re running Liferay DXP locally on port 8080, this is http://localhost:8080/o/com.liferay.docs.guestbook.service/api/axis.This page contains links to the WSDL (Web Services Description Language) documents for the Guestbook and Entry remote service methods. WSDL files describe details about the remote service methods, including the type of data these methods require.
If you want to make your app’s services available for remote invocation via SOAP, generating WSDD and WSDL files is required. For example, the Liferay Mobile SDK relies on the WSDD and WSDL to discover your Liferay DXP app’s remote services. For the Liferay Mobile SDK to create a mobile client that can access your Liferay DXP app’s web services, you must therefore generate a WSDD and WSDL for your app.
Next, you’ll learn how to secure your web services. Unless you secure your web services by implementing permission checks, any user can add, update, or delete guestbooks or guestbook entries, and you certainly don’t want that.