This posting helps you with the process of migrating Liferay ServiceBuilder projects from SDK to Maven. Migrating Liferay plugin projects from the Liferay SDK to Maven is normally a straight-forward process. ServiceBuilder plugins, however, are structured differently under Maven than the SDK counterparts.

When you create a ServiceBuilder portlet using the liferay-servicebuilder-archetype, the Maven project is a multi-module project. There is one main project but underneath it has two child modules.

The first module is the service implementation module. Following best practices for ServiceBuilder, this module is a simple portlet plugin, but it contains the service.xml file and all of the implementation classes for the services.

The second module is for the service jar. Since the service jar will be shared with other projects, it is managed as a separate Maven module so it can build, install, and deploy the service jar artifact.

Maven Options

When running ServiceBuilder under Maven, I often run into permgen failures when working on large service.xml files.

The easiest way to prevent this kind of failure is to set a MAVEN_OPTS environment variable with the memory settings you want to use. In general I’ve been successful using “-Xmx1024m -XX:MaxPermSize=512m”.

If you set as an environment variable, you won’t have to worry about them in the future.

Creating The Project

To create a new ServiceBuilder project, use the liferay-servicebuilder-archetype. Group id and version are yours to define, but for the artifact name, do not add the “-portlet” (or other) suffix. The archetype will add suffixes to the submodules for you.

If you have any dependencies from liferay-plugin-package.properties to set up, add the dependencies to the “-portlet” subproject’s pom.xml file. They are not dependencies for the parent project nor the “-service” subproject, so there’s no reason to add them anywhere else. See the separate blog entry about migrating dependencies for further information.

Initial Copy Task

You might think that you should just dive in and copy files here and there up front and then do the build. I actually recommend copying in two phases.

Initially, you just want to copy your service.xml file from the docroot/WEB-INF folder of your SDK project to the “-portlet” subproject’s src/main/webapp/WEB-INF directory, and only that file. After it is copied, use the mvn liferay:build-service command to have ServiceBuilder create all of your initial files.

Separating this step from the rest of the copying gives you an initial framework for your service code in the “-portlet” submodule and the “-service” submodule. Basically you’re setting up a solid foundation for the ServiceBuilder project.

Next Copy Task

Next you will be copying files, classes, etc. from your SDK project to the “-portlet” subproject.  You won’t have to worry about the “-service” subproject. Later on we’ll be re-running ServiceBuilder to ensure everything is good in the project.

Java sources from the docroot/WEB-INF/src directory will be copied to the “-portlet” subproject’s src/main/java folder. You can either copy all of the code or be selective in what is copied. I usually know which Impl classes I’ve changed, so I’ll copy those individually rather than just grabbing everything. But it’s your call.

Non-Java files from docroot/WEB-INF/src should be copied to the “-portlet” subproject’s src/main/resources folder. This will be where all of the Spring config files, the properties files, etc., end up.

Copy other files from the docroot directory into the “-portlet” subproject’s src/main/webapp directory as appropriate. Do not copy the lib, tld, src, service or classes directories from the docroot/WEB-INF folder.

Building

After everything has been copied, issue the mvn liferay:build-service command to rebuild the services. This will make changes to the “-service” submodule so it has the complete API in the service jar.

Normally this will complete with no failures, but if you get a build failure you’ll have to fix it before going on.

You can use the mvn package command to build all of the artifacts. Note that the package task does not include running the liferay:build-service task, so you should do this separately. This step will be necessary when you change one of the source files or service.xml from the “-portlet” submodule.

This too should complete successfully, but if you have errors here they’re likely due to missing dependencies.

Sharing the Service Jar

Maven does dependency retrievals from a repository, either a local one on the filesystem (for cached dependencies) or from a remote one (so it can download dependencies from the net that have not been used before).

To use the service jar as a dependency in another project, you need to add it to a repository. The easiest way is to add it to the local repository using the mvn install command. If your environment has a shared artifact repository (i.e. Nexus or Artifactory), you can use the mvn deploy command to deploy your service jar to the remote repository (assuming you’ve configured it correctly in your pom.xml file).

When this is done, you can then use your service jar as a dependency in other projects just by adding it to the pom.xml file:

<dependency>
   <groupId>com.example.liferay</groupId>
   <artifactId>customer-portlet-service</artifactId>
   <version>1.0.0.0</version>
</dependency>

Since this will be deployed within your war file, do not use the provided scope.

A Note About Versioning

You’re going to want to increment the version on your ServiceBuilder project every time you build services. As you add or change methods in the implementation, other projects may not get the update because of the local repository cache.

If you increment the version in the project and change the dependency version in other projects, you won’t end up with stale service jars.

Be aware, however, that this is a real pain, especially early in the development cycle when the service layer is in active development. When you change the version after building services day after day after day, and then consumers have to update the dependency version day after day after day – well, this gets old real quick. You’ll be tempted to discard this version update activity (trust me, I know from experience).

But if you don’t do this kind of active versioning, your service consumers will get compile-time errors for no such methods, signature mismatch errors, etc. This can be solved by doing a mvn clean package to clean everything out and start over, but sometimes you’ll have to actually go into your local repository and purge the cached jar file.

Handling Global Service Jars

There are times when your service jar will be deployed globally. Typically this is when you have a hook that needs access to your custom service, or you have a large number of service consumers and you don’t want to do a lot of redeploys when the service jar changes.

When you have this situation, add the provided scope to the “-portlet” submodule’s pom.xml service jar dependency. Maven will then build the implementation war but will not include the service jar. This simplifies deployment versus the SDK. The SDK always included the service jar, so you’d have to delete the jar manually in order for all plugins to use the global jar exclusively.