Liferay 7 Development, Part 4

Introduction

In part 3 of the blog, the API for the Filesystem Access project was flushed out.

In this part of the blog, we'll create the service implementation module.

The Data Transfer Object

Now we get to implement our DTO object.  The interface is pretty simple, we just have to add all of the methods and expose values from data we retain.

The code will be available on GitHub so I won't go into great detail here.  Suffice it to say that for the most part it is exposing values from the underlying File object from the filesystem.

The Service

The service implementation is just as straight forward as the DTO.  It leverages the java.io.* packages and apis and also uses com.liferay.portal.kernel.util.FileUtil for some supporting functions.  It also integrates the use of the Liferay auditing mechanism to issue relevant audit messages.

The Annotations

It's really the annotations which will make the FilesystemAccessServiceImpl into a true DS service.

The first annotation is the Component annotation and it is used such as:

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

/**
 * class FilesystemAccessServiceImpl: Implementation class of the FilesystemAccessService.
 *
 * @author dnebinger
 */
@Component(service = FilesystemAccessService.class)
public class FilesystemAccessServiceImpl implements FilesystemAccessService {

Here we are declaring that we have a DS component implementation that provides the FilesystemAccessService.  And boom, we're done.  We have just implemented a DS service.  Couldn't be easier, could it?

The other annotation import there is the Reference annotation.  We're going to be showing how to use the DS service in the next part of the blog, but we'll inject a preview here.

In the discussion of the service in the previous section, we mentioned that we were going to be integrating the Liferay audit mechanism so different filesystem access methods would be audited.  To integrate the audit mechanism, we need an instance of Liferay's AuditRouter service.  But how do we get the reference?  Well, AuditRouter is also implemented as a DS service.  We can have OSGi inject the service when our module is started using the Reference annotation such as:

/**
 * _auditRouter: We need a reference to the audit router
 */
@Reference
private AuditRouter _auditRouter;

And boom, we're done there too.

OSGi is handling all of the heavy lifting for us.  One annotation exposes our class as a service component implementation and another annotation will inject services which we have a dependency on.

Configure The Module

So we're not quite finished yet.  We have the code done, but we should configure our bundle so we get the right outcome.

Edit the bundle file so it contains the following:

Bundle-Name: Filesystem Access Service
Bundle-SymbolicName: com.liferay.filesystem.access.svc
Bundle-Version: 1.0.0
-sources: true

The Liferay standard is to give a meaningful name for the bundle, but the symbolic name should be something akin to the project package.  You definitely want the symbolic name to be unique when it is deployed to the OSGi container.

Deployment

Well the modifications are all done.  At a command prompt, go to the modules/apps/filesystem-access-svc directory and execute the following command:

$ ../../../gradlew build

You should end up with your new com.liferay.filesystem.access.svc-1.0.0.jar bundle file in the build/libs directory.  If you have a Liferay 7 CE or Liferay DXP tomcat environment running, you can drop the jar into the Liferay deploy folder.

Drop into the Gogo Shell and you can even verify that the module has started:

Welcome to Apache Felix Gogo

g! lb | grep Filesystem
  486|Active     |   10|Filesystem Access API (1.0.0)
  487|Active     |   10|Filesystem Access Service (1.0.0)

So we see that our module deployed and started correctly, so all is good.  So we have a good outcome because both of our modules have been deployed and started successfully.

When things go awry.  If you've only deployed the service module and not the api module, you'll see something like:

Welcome to Apache Felix Gogo

g! lb | grep Filesystem
  487|Installed  |   10|Filesystem Access Service (1.0.0)

This is really a bad outcome.  Installed simply means it's there in the OSGi container, but it won't be available.  We can try to start the module:

g! start 487
org.osgi.framework.BundleException: Could not resolve module: com.liferay.filesystem.access.svc [487]
  Unresolved requirement: Import-Package: com.liferay.filesystemaccess.api; version="[1.0.0,2.0.0)"

This is how you see why your module isn't started.  These are the kinds of messages you'll see most often, some sort of missing dependency that you'll have to satisfy before your module will start.  The worst part is that you won't see these errors in the logs either.  From the logs perspective you won't see a "STARTED" message for your module, but as we all know the lack of a message is not really a visible error that is easily resolved.

This current error is easy to fix - just deploy the api module.  As soon as it is started, OSGi will auto-start the service module, you won't have to start it yourself.  You can check that both modules are running by listing the beans again and you'll see they are both now marked as Active.

Conclusion

So we have our DS service completed.  The API module defining the service is out there and active.  We have an implementation of the service also deployed and active, too.  If there was a need for it, we could build alternative implementations of the DS service and deploy those also, but that's a discussion that should be saved for perhaps a different blog entry.

So we have our DS service, we just don't have a service client yet.  Well that's coming in the next part of the blog.  We're actually going to start building the filesystem access portlet and layer in our client code.  See you there...