Creating a Spring MVC Portlet War in the Liferay Workspace

Introduction

So I've been working on some new Blade sample projects, and one of those is the Spring MVC portlet example.

As pointed to in the Liferay documentation for Spring MVC portlets, these guys need to be built as war files, and the Liferay Workspace will actually help you get this work done. I'm going to share things that I learned while creating the sample which has not yet been merged, but hopefully will be soon.

Creating The Project

So your war projects need to go in the wars folder inside of your Liferay Workspace folder, basically at the same level as your modules directory. If you don't have a wars folder, go ahead and create one.

Next we're going to have to manually create the portlet project. Currently Blade does not have support for building a Spring MVC portlet war project; perhaps this is something that can change in the future.

Inside of the wars folder, you create a folder for each portlet WAR project that you are building. To be consistent, my project folder was named blade.springmvc.web, but your project folder can be named according to your standards.

Inside your project folder, you need to set up the folder structure for a Spring MVC project. Your project structure will resemble:

Spring MVC Project Structure

For the most part this structure is similar to what you would use in a Maven implementation. For those coming from a legacy SDK, the contents of the docroot folder go to the src/main/webapp folder, but the docroot/WEB-INF/src go in the src/main/java folder (or src/main/resources for non-java files).

Otherwise this structure is going to be extremely similar to legacy Spring MVC portlet wars, all of the old locations basically still apply.

Build.gradle Contents

The fun part for us is the build.gradle file. This file controls how Gradle is going to build your project into a war suitable for distribution.

Here's the contents of the build.gradle file for my blade sample:

buildscript {
  repositories {
    mavenLocal()
    maven {
      url "https://cdn.lfrs.sl/repository.liferay.com/nexus/content/groups/public"
    }
  }

  dependencies {
    classpath group: "com.liferay", name: "com.liferay.gradle.plugins.css.builder", version: "latest.release"
    classpath group: "com.liferay", name: "com.liferay.css.builder", version: "latest.release"
  }
}

apply plugin: "com.liferay.css.builder"

war {
  dependsOn buildCSS

  exclude('**/*.scss')

  filesMatching("**/.sass-cache/") {
    it.path = it.path.replace(".sass-cache/", "")
  }

  includeEmptyDirs = false
}

dependencies {
  compileOnly project(':modules:blade.servicebuilder.api')
  compileOnly 'com.liferay.portal:com.liferay.portal.kernel:2.6.0'
  compileOnly 'javax.portlet:portlet-api:2.0'
  compileOnly 'javax.servlet:javax.servlet-api:3.0.1'
  compileOnly 'org.osgi:org.osgi.service.component.annotations:1.3.0'
  compile group: 'aopalliance', name: 'aopalliance', version: '1.0'
  compile group: 'commons-logging', name: 'commons-logging', version: '1.2'
  compileOnly group: 'javax.servlet.jsp.jstl', name: 'jstl-api', version: '1.2'
  compileOnly group: 'org.glassfish.web', name: 'jstl-impl', version: '1.2'
  compile group: 'org.springframework', name: 'spring-aop', version: '4.1.9.RELEASE'
  compile group: 'org.springframework', name: 'spring-beans', version: '4.1.9.RELEASE'
  compile group: 'org.springframework', name: 'spring-context', version: '4.1.9.RELEASE'
  compile group: 'org.springframework', name: 'spring-core', version: '4.1.9.RELEASE'
  compile group: 'org.springframework', name: 'spring-expression', version: '4.1.9.RELEASE'
  compile group: 'org.springframework', name: 'spring-webmvc-portlet', version: '4.1.9.RELEASE'
  compile group: 'org.springframework', name: 'spring-webmvc', version: '4.1.9.RELEASE'
  compile group: 'org.springframework', name: 'spring-web', version: '4.1.9.RELEASE'
}

So first is the buildScript and the CSS builder apply line followed by war customization stanza.

These parts are currently necessary to support compiling the SCSS files into CSS and store the files in the right place in the WAR. Note that Liferay currently sees this manual execution of the CSS builder plugin as a bug and plan on fixing it sometime soon.

Managing Dependencies

The next part is the dependencies, and this will be the fun part for you as it was for me.

You're going to be picking from two different dependency types, compile and compileOnly. The big difference is whether the dependencies get included in the WEB-INF/lib directory (for compile) or just used for the project compile but not included (compileOnly).

Many of the Liferay or OSGi jars should not be included in your WEB-INF/lib directory such as portal-kernel or the servlet or portlet APIs, but they are needed for compiles so they are marked as compileOnly.

In Liferay 6.x development, we used to be able to use the portal-dependency-jars in liferay-plugin-package.properties to inject libraries into our wars at deployment time. But not for Liferay 7 CE/Liferay DXP development.

The portal-dependency-jars property in liferay-plugin-package.properties is deprecated in Liferay 7 CE/Liferay DXP. All dependencies must be included in the war at build time.

Since we cannot use the portal dependencies in liferay-plugin-package.properties, I had to manually include the Spring jars using the compile type. 

Conclusion

Yep, that's pretty much it.

Since it's in the Liferay Workspace and is Gradle-built, you can use the gradle wrapper script at the root of the project to build everything, including the portlet wars.

Your built war will be in the build/libs directory in project, and this war file is ready to be deployed to Liferay by dropping it in the Liferay deploy folder.

Debugging "ClassNotFound" exceptions, etc, in your war file can be extremely challenging since Liferay doesn't really keep anything around from the WAR->WAB conversion process. If you add the following properties to portal-ext.properties, the WABs generated by Liferay will be saved so you can open the file and see what jars were injected and where the files are all found in the WAB.

module.framework.web.generator.generated.wabs.store=true
module.framework.web.generator.generated.wabs.store.dir=${module.framework.base.dir}/wabs

If you want to check out the project, it is currently live in the blade samples: https://github.com/liferay/liferay-blade-samples/tree/master/liferay-workspace/wars/blade.portlet.springmvc.

Blogs
sound nice , out ide team will consider if adding such template.
Hi David,

In my situation I have portlet which created in version 6.1 with Ant and now I want to convert it into DXP using gradle. Is it possible ?

Please guide me .

Thanks
Hi David, very nice post, thank you! Btw, what would be the best practice if I have several separate Spring MVC portlet war? How best to share the common Spring libs? Thanks!
Hi Huage,

Here is your answer for sharing common spring libs - http://www.learnandpost.com/2017/04/spring-osgi-portlet-or-bundle-with.html

Hope it helps emoticon

when i do this, i got this error : Execution failed for task ':wars:SpringMvcPortletFormation:compileJava'. > Could not resolve all files for configuration ':wars:SpringMvcPortlet:compileClasspath'.    > Could not find javax.validation:validation-api:.      Required by:          project :wars:SpringMvcPortlet    > Could not find org.hibernate.validator:hibernate-validator:.      Required by:          project :wars:SpringMvcPortlet