Sharing classes between Liferay plugins with Maven

>> Thursday, April 26, 2012

A Java portal technology such as Liferay poses several challenges with respect to class loading. With Liferay we can implement different bits of functionality or design such as layouts, themes, portlets, services and hooks and package them inside plugins to be deployed on a portal instance. Each plugin is packaged as a WAR file. Unfortunately, the original JEE specification for servlet containers did not identify the need for different war files to "see" each other, quite the contrary. In Java, this means that each war file has its own classloader. One solution to this problem is to package all interdependent components (services, portlets etc) inside the same war. This is certainly not desirable from a modularity perspective. To alleviate this problem, Liferay proposes the use of Class Loader Proxies (CLPs) by which Java beans and services in one war can be used in another one.  The main caveat however is that this approach requires the deployment of a library (jar) containing all interface classes to the common lib of the servlet container so they get loaded by the container class loader. This would not be such a problem but unfortunately, redeployment of the common jars generally requires a recycling of the servlet container.  This solution is described partly in the following Wiki article: Using Class Loader Proxy classes to share plugins services.

In this article, I will explain how I setup my Maven projects so as to facilitate the sharing of plugin services. The scenario is typically the following: we need to define a number of service builder services and deploy them to our portal inside a war. We also want to be able to implement and deploy a number of other plugins that use or depend on these services. So here is how we do it.

Configuring the Base Plugin

Our first plugin that we call "abc-common.war" contains the ABC services defined and generated by the LR service builder. It does not have any UI component per say but it must be deployed in order to provide ABC services. Our maven pom.xml file associated with this project generates two deployable artifacts: a WAR for the plugin and a JAR to be made available in the classpath of the container (ex: $TOMCAT_HOME/lib/ext). The jar is a secondary artifact with a "service" classifier (i.e. abc-common-service.jar).

To make sure that abc-common.war and abc-common-service.jar each contain the required Java constructs, we need to configure the war and jar plugins in our POM as presented here:

 <plugin>  
      <artifactId>maven-war-plugin</artifactId>  
      <configuration>  
           <webResources>  
                <resource>  
                     <directory>src/main/webapp/WEB-INF</directory>  
                     <filtering>true</filtering>  
                     <targetPath>WEB-INF</targetPath>  
                </resource>  
           </webResources>  
      </configuration>  
      <executions>  
           <execution>  
                <phase>package</phase>  
                <goals>  
                     <goal>war</goal>  
                </goals>  
                <configuration>  
                     <packagingExcludes>  
                          WEB-INF/classes/**/abc/*Exception.class,  
                          WEB-INF/classes/**/abc/model/*,  
                          WEB-INF/classes/**/abc/service/*.class,  
                          WEB-INF/classes/**/abc/service/persistence/*Persistence.class,  
                          WEB-INF/classes/**/abc/service/persistence/*Util.class  
                     </packagingExcludes>  
                </configuration>                                
           </execution>  
      </executions>  
 </plugin>  
 <plugin>  
      <groupId>org.apache.maven.plugins</groupId>  
      <artifactId>maven-jar-plugin</artifactId>  
      <executions>  
           <execution>  
                <phase>package</phase>  
                <goals>  
                     <goal>jar</goal>  
                </goals>  
                <configuration>  
                     <classifier>service</classifier>  
                     <includes>  
                          <include>**/abc/*Exception.class</include>  
                          <include>**/abc/model/*</include>  
                          <include>**/abc/service/*</include>  
                          <include>**/abc/service/persistence/*</include>  
                     </includes>  
                     <excludes>  
                          <exclude>**/abc/model/impl/**</exclude>  
                          <exclude>**/abc/service/base/**</exclude>  
                          <exclude>**/abc/service/impl/**</exclude>  
                          <exclude>**/abc/service/persistence/*Impl*</exclude>  
                     </excludes>  
                </configuration>  
           </execution>  
      </executions>  
 </plugin>  

In the above POM fragment, one should note the use of "exclude " and "inlude" patterns. These must be adjusted to the context. Also, note the use of a jar classifer to define the modules's secondary artifact.
The war file must be deployed as any other Liferay plugin. The jar file must be deployed to the common classpath of the container as noted above.

Depending Plugins

Other plugins depending upon the first one should declare their dependency in the liferay-plugin-package.properties as such (this is something new in LR 6.1):

required-deployment-contexts=\
abc-common

A plugin (abc-client.war) that depends on ABC Services must declare the following dependency in it's POM file:

 <dependency>  
      <groupId>com.opnworks.tavel</groupId>  
      <artifactId>abc-common</artifactId>  
      <classifier>service</classifier>  
      <version>$VERSION</version>  
      <scope>provided</scope>  
 </dependency>  

Et voila! All classes inside the abc-client project should be able to "see" ABC Common interfaces and services at compile time and should be able to use them at runtime providing the ABC services were properly deployed.

Have fun in the Liferay lane...

3 commentaires:

Lorenzo Bugiani 23 May, 2013 04:43  

Hi!

I know this post is quite old, but it is very interesting for me, so I would like to understand more.

Exactly, how does it work? Why I have to deploy both the war and the jar?
I mean, if I manually deploy the generated jar into the tomcat's lib, the jar is not automatically shared through other plugins?

Thanks in advance

Laurent Gauthier 23 May, 2013 21:43  

Lorenzo,

The idea is that you want to benefit from the fact that your services are in a LR "plugin" that is managed and made visible in LR as a plugin (versioning, database update, permissions, etc). You also want to "expose" these services to other plugins and to do this you need to deploy a jar containing the API in the containers lib.

Lorenzo Bugiani 07 June, 2013 07:08  

I don't understand, sorry.

What do you exactly mean when you say that deployng both war and jar, the services are LR plugin and exposed to other plugin?

If I only deploy the jar to the tomcat lib, what are the functionality that I lose (or miss... sorry for my poor english)?

Finally, there are't conflict problem having same class and services both in LR plugin and simple jar into the tomcat lib?

thanks again!

Post a Comment

  © Blogger template Webnolia by Ourblogtemplates.com 2009

Back to TOP