>> 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 PluginOur 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 PluginsOther 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):
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...