Introduction

So I wrote https://liferay.dev/blogs/-/blogs/extending-liferay-osgi-modules five years ago targeting Liferay 7.0.

A lot has changed since then. Liferay Gradle Workspace plugin has seen numerous updates. Gradle has seen an update or two as well.

Recently when asked to assist a client who wanted to extend Liferay's Journal Article service, I started by recommending they look at this old blog post for guidance.

However, I was a bit surprised when they came back after giving it a try and reported that the technique described in the blog was not working for them. I shouldn't have been surprised, given the changes that had happened since the post went up 5 years ago.

After reviewing the issue and finding a resolution, I figured it was time to come back and revisit the method for extending Liferay OSGi modules...

The Goal

The goal is usually pretty straight forward - you need to override a Liferay DXP class that is contained in a jar that is contained in an LPKG file, you can't replace it using an @Component override nor can you extend it because it is in an internal package.

For these cases, you need a Marketplace Override, or basically a jar to replace Liferay's module jar named in a specific way and dropped into the osgi/marketplace/override directory.

The challenge, of course, is how to build it...

For the current client, they wanted to override the com.liferay.journal.internal.upgrade.util.JournalArticleImageUpgradeHelper class from the com.liferay.journal.service module.

What follows is how we did it...

Creating the Module

Again, in a Liferay Workspace, we'll need a basic Gradle-based module.

In the src/main/java directory we created the overriding com.liferay.journal.internal.upgrade.util.JournalArticleImageUpgradeHelper class.

Following the old blog, we had the following build.gradle file:

dependencies {
   compileOnly group: "com.liferay.portal", name: "release.dxp.api"

   compile group: "com.liferay", name: "com.liferay.journal.service", version: "6.0.72"
}

jar.archiveName = 'com.liferay.journal.service.jar'

We were targeting Liferay DXP 7.2, and 6.0.72 was the version that was in the FixPack we were building for.

And like the old blog, we also had the following bnd.bnd file:

Bundle-Name: Liferay Journal Service
Bundle-SymbolicName: com.liferay.journal.service
Bundle-Version: 6.0.72
Liferay-Require-SchemaVersion: 3.4.0
Liferay-Service: true
-dsannotations-options: inherit

Include-Resource: @com.liferay.journal.service-6.0.72.jar

The Problem

So we followed the old blog and the module built and deployed correctly, but the overriding class was just not being used.

I opened up the built jar and found that the overriding class, although being compiled as part of the build process, was not being included correctly in the jar. Actually what I think happened, the Include-Resource header was doing just that, it was exploding and pulling in all of the files from the Liferay jar, basically replacing the custom one with the legacy one when building the jar.

So even though my project had files in the right places, they were getting stomped on by the legacy files when the jar was being built.

The Solution

Clearly the Include-Resource header was causing me problems. After conferring with my good friend Ray Auge (and per his guidance), I switched up to a directive in my bnd.bnd file:

Bundle-Name: Liferay Journal Service
Bundle-SymbolicName: com.liferay.journal.service
Bundle-Version: 6.0.72
Liferay-Require-SchemaVersion: 3.4.0
Liferay-Service: true
-dsannotations-options: inherit

-includeresource: @com.liferay.journal.service-6.0.72.jar!/!com/liferay/journal/internal/upgrade/util/JournalArticleImageUpgradeHelper.class

So here I've switched from Include-Resource header to -includeresource directive. This directive has significantly more configuration options on it and is more powerful than the header itself: https://bnd.bndtools.org/instructions/includeresource.html

Specifically, the include resource directive above reads: "From the exploded com.liferay.journal.service-6.0.72.jar dependency, include all files except excluding the JournalArticleImageUpgradeHelper.class".

The @ sign in front of the jar means it should be exploded and not just injected as-is.

Following the first ! character is the path to include, by using "/" alone we're saying that all files should be included.

The second ! marks the exclusion rule where we have specified the path to the class file that should not be pulled forward.

The documentation page for the -includeresource directive has lots of other details for different ways you can structure the arguments to get the outcome you need.

Conclusion

With this new -includeresource directive in the bnd.bnd file, when the jar was built almost all of the files from the legacy jar were included in the new module jar (all except for the class we didn't want to bring forward), and the custom class was in the module instead.

The new module jar, when dropped into the osgi/marketplace/override folder, injected the necessary override logic and the desired outcome was achieved.

Switching from the Include-Resource header to the -includeresource directive made all the difference. It's also notable that at Liferay the engineering team now exclusively uses the directive (well, you can see the header used in test modules, but otherwise it is all the directive).

Hopefully this will come in handy in case you also need to build a Liferay override module.

6
Blogs

@David In case of Multiple classes what will be the approach  For Example i have two classes  1. MFAPolicy.class 2. MFASystemConfiguration.class how to add that in include resource ?  Below is the code i have added that does-not work -includeresource: @com.liferay.multi.factor.authentication.web-1.0.24.jar!/com/liferay/multi/factor/authentication/web/internal/policy/MFAPolicy.class!/com/liferay/multi/factor/authentication/web/internal/system/configuration/MFASystemConfiguration.class

Great post David! However I have noticed that when the new module's jar file is already in the /osgi/modules folder from being installed while the instance was running, upon stopping and starting the server again, the original module is the active one and this error is printed out in the console: Unable to install bundle at file:/C:/.../com.liferay.frontend.js.spa.web.jar org.osgi.framework.BundleException: A bundle is already installed with the name "com.liferay.frontend.js.spa.web" and version "5.0.45" The issue is solved after a gradle clean and deploy again, but is there anyway to avoid this? We're on dxp-2024.q1.1

Okay, so the content is a bit out of date, and it looks like you might be deploying to the wrong place anyways...

When this post first came out, when you would create an override module for one of Liferay's modules, you'd have to drop it into the osgi/marketplace/override folder. However, this only works when you have an osgi/marketplace folder that is filled with .lpkg files.

In the last release of 7.3 and all releases of 7.4, Liferay has ditched the use of .lpkg files, now putting the raw jars into the osgi/portal directory.

In this new world, when you create an override module for one of Liferay's jars, you need to put your jar into the osgi/portal directory also, not osgi/modules or osgi/marketplace/override. When you put your jar into osgi/portal, it must completely replace Liferay's version (i.e. you can't keep the old version, it must be completely gone).

The problem with this that you face is when you do upgrades, you have to remember to rebuild your override module jar and then put it into the new bundle's osgi/portal directory, replacing or removing Liferay's old version.

It's a pain in the butt, certainly, but they intend it to be that way. This is the worst way to try to override Liferay and most of the time there are better options than this technique.

If you want to share what you're trying to override and discuss alternatives, find me on the Community Slack and I'll be happy to review with you. There's also my bi-weekly Office Hours sessions where we can talk live (find details in one of my blog posts or the #community channel of the Liferay Community Slack.

 

Hi, I'm trying to override the ManagementToolbarPropsTransformer.js file in com.liferay.journal.web, but I can't find a way to do it. Can you help me? Is there a post about this?

Thanks