Creating Plugins to Extend Plugins - The Maven Way

Extending Liferay core functionality is rather easy. You can use hook or (if needed) ext plugins to extend the functionality.

But what if you want to extend a custom plugin (e.g. one you downloaded from the Liferay Marketplace)? Of course you could download the sources of that plugin and extend it to your needs. But this way you cannot separate your extensions from the original plugin. So if a new version of that plugin is released, it would be rather difficult (if not impossible) to merge your extensions. 

The Liferay Portal 6.2 Developer's Guide shows a way how to extend custom plugins when using the Liferay SDK, which is based on Ant. You just have to put the property original.war.file into your build.xml and you're done.

However if your build pipeline is based on Maven, the Ant approach would not work. So how can you extend custom plugins when using Maven?

The answer to this question is to use Maven War Overlays. By this you can extend custom plugins and just replace the parts you really need. 

Providing the custom plugin in your Maven repository

Unfortunately most liferay plugins are not available as Maven artifacts in any public maven repository. So you need to provide them in your local repository (or, if you want, put them in your company repository).

As an example I will use the Social Networking Plugin. Provided that you already downloaded the plugin from the Marketplace you got a file called Social Networking CE.lpkg. Just unzip it to get the WAR file social-networking-portlet-6.2.0.2.war

Now install this WAR file to your local Maven repository using the install:install-file target. If you want to deploy it to your company repository, you can use the deploy:deploy-file target. Just be sure to choose a proper groupId, artifactId and the proper version

mvn install:install-file -Dfile=social-networking-portlet-6.2.0.2.war -DgroupId=com.liferay.plugins -DartifactId=social-networking-portlet -Dversion=6.2.0.2 -Dpackaging=war

If you just want to update JSP files, you're done here so far. However if you want to use or extend the Java classes or even the Service layer, you have to install those artifacts, too. 

So next step is to unzip the WAR file.

unzip social-networking-portlet-6.2.0.2.war

The Java classes are contained in WEB-INF/classes, so create a new JAR file from that folder and install it into your maven repository. I prefer to use a classifier classes to be able to distinguish between the Java classes and the service layer.

cd WEB-INF/classes
zip -r social-networking-portlet-6.2.0.2-classes.jar *
mvn install:install-file -Dfile=social-networking-portlet-6.2.0.2-classes.jar -DgroupId=com.liferay.plugins -DartifactId=social-networking-portlet -Dversion=6.2.0.2 -Dpackaging=jar -Dclassifier=classes

The service layer is present in WEB-INF/lib. Just put the service JAR into your maven repository. Use the classifier service here.

cd WEB-INF/lib
mvn install:install-file -Dfile=social-networking-portlet-service.jar -DgroupId=com.liferay.plugins -DartifactId=social-networking-portlet -Dversion=6.2.0.2 -Dpackaging=jar -Dclassifier=service

Creating your Maven project

After installing all needed artifacts to your Maven repository you can setup your Maven project. For example you can use a simple Maven archetype like the liferay-portlet-archetype to create a new Maven portlet. 

Now add dependencies to the artifacts mentioned above. Just add the following entries to your <dependencies> section.

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns=" xmlns:xsi=" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 >
    <modelVersion>4.0.0</modelVersion>
    ...
    <dependencies>
        <dependency>
            <groupId>com.liferay.plugins</groupId>
            <artifactId>social-networking-portlet</artifactId>
            <version>6.2.0.2</version>
            <type>war</type>
        </dependency>
        <dependency>
            <groupId>com.liferay.plugins</groupId>
            <artifactId>social-networking-portlet</artifactId>
            <version>6.2.0.2</version>
            <type>jar</type>
            <classifier>classes</classifier>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>com.liferay.plugins</groupId>
            <artifactId>social-networking-portlet</artifactId>
            <version>6.2.0.2</version>
            <type>jar</type>
            <classifier>service</classifier>
            <scope>provided</scope>
        </dependency>         
    </dependencies>
</project>    
Last step is to configure the maven-war-plugin. That will be configured to use the WAR file from the social-networking-portlet as a base and then merge it with your project. So add the following to your plugins section in your pom.xml.
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns=" xmlns:xsi=" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 >
    <modelVersion>4.0.0</modelVersion>
    ...
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-war-plugin</artifactId>
                <configuration>
                    <overlays>
                        <overlay>
                            <groupId>com.liferay.plugins</groupId>
                            <artifactId>social-networking-portlet</artifactId>
                        </overlay>
                    </overlays>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>    

You're done. When packaging your project the maven-war-plugin will merge your changes with the original portlet. If a new version of the plugin is released, just repeat the steps above to provide the new version in your maven repository. Update the version numbers in your pom.xml and you will use the latest version as the base of your project.

Happy coding!

Final Notes

  • The hints shown in the Liferay Portal 6.2 Developer's Guide still apply to the Maven way. So if you are using a different servlet context you have to overwrite the ClpSerializer.java class. 
  • If your custom plugin should completely replace the original portlet within your portal, just give it the same name and the same version as the original portlet. 
  • Be sure that the licence of the portlet you want to extend allows you to do this.
Blogs
Hey Dominik, thanks for the thoughtful and informative post! One question: does it depend on the plugin's sources being in the .war file? e.g. in the Social networking plugin, there are tons of *.java files. What if the plugin you wish to extend has no source code?
The Maven War Overlay merges (respectively replaces) Web Resources (.xml, .properties, .jsp files and so on) and the Class files (.class). So it is not neccessary to have the source code of the original plugin.

For developers it could be useful to have the source code, especially when replacing or extending existing Java classes in the original plugin. But it is not needed for my approach.
Thank you for your post.
I tried this with the calendar portlet (EE) from the Marketplace - unfortunately without success.
I get at few errors in catalina.out when deploying the extended plugin:
Warning: META-INF/MANIFEST.MF modified in the future.
...(a lot more of similar)
Warning: WEB-INF/classes/com/liferay/calendar/model modified in the future.
...
09:23:02,714 INFO [localhost-startStop-3][HotDeployEvent:145] Plugin UU-Calendar-portlet requires marketplace-portlet
...
09:23:04,407 ERROR [localhost-startStop-3][HotDeployImpl:233] com.liferay.portal.kernel.deploy.hot.HotDeployException: Error registering portlets for UU-Calendar-portletUU-Calendar-portlet
..
Caused by: java.lang.LinkageError: loader constraint violation: loader (instance of org/apache/catalina/loader/WebappClassLoader) previously initiated loading for a different type with name "javax/portlet/PortletConfig"

Any help is appreciated (I'm using Liferay 6.2)
Best regards, Andreas
The "modified in the future" warnings indicate that the timezone on your liferay server is incorrect. Check that the time in the server is the same as in your development machine.

The LinkageError could be a problem if you try to deploy the same portlet twice (the original one and the modified one). Try to uninstall the original Calendar portlet first.
I also try to customize (with just a JSP hook) the liferay calendar portlet for liferfay version 6.2 with maven. I did everything according to your blog post, but now I don't know which are the next steps .
For a normal JSP hook I would add a jsp hook configuration and select the JSP files from Liferay that I want to hook (in the Liferay IDE). But what should I do in this case of an external plugin?
Thanks for you post Dominik, was very useful for override calendar-portlet.

I have added a maven project which override calendar tin case of someone need to follow your steps for this portlet.

https://drive.google.com/file/d/0B_rBM3f4UT69UTBQM0VOVHJmLTg/view?usp=sharing

Kind regards.