Ask Questions and Find Answers
Important:
Ask is now read-only. You can review any existing questions and answers, but not add anything new.
But - don't panic! While ask is no more, we've replaced it with discuss - the new Liferay Discussion Forum! Read more here here or just visit the site here:
discuss.liferay.com
RE: Remote staging customization
Hi team,
In Liferay 7.0, we are trying to customize the LayoutRemoteStagingBackgroundTaskExecutor class., but we have not seen an extension point for it. It doesn't seem to be exported by its module, but it is being loaded through BackgroundTaskExecutorConfigurator, that creates an instance of the LayoutRemoteStagingBackgroundTaskExecutor, and it registers it as follows:
ServiceRegistration<BackgroundTaskExecutor> serviceRegistration =
bundleContext.registerService(
BackgroundTaskExecutor.class, backgroundTaskExecutor,
properties);
Using scr:info com.liferay.portal.background.task.internal.BackgroundTaskExecutorRegistryImpl, I see the reference:
Reference Properties:
background.task.executor.class.name = com.liferay.exportimport.background.task.LayoutRemoteStagingBackgroundTaskExecutor
objectClass = [com.liferay.portal.kernel.backgroundtask.BackgroundTaskExecutor]
service.bundleid = 623
service.id = 1106
service.scope = singleton
After several tries to replace the LayoutRemoteStagingBackgroundTaskExecutor by our custom one, we decided to replace the BackgroundTaskExecutorRegistryImpl to register the custom class.
We created the custom version with isolation level at company instead of group, and we see the correct isolation level in the logs and its outcome. We are loading the original executor with:
@Reference (
unbind = "-",
target = "(component.name=com.liferay.exportimport.background.task.LayoutRemoteStagingBackgroundTaskExecutor)"
)
protected BackgroundTaskExecutor _defaultExecutor;
And we use it when we want the default functionality.
However, this approach has two issues:
1) We added extra logs on the execute() method, and we are not seeing them. However, the background task is executed as expected, so we assume that at the time the execute() method is fired, the original LayoutRemoteStagingBackgroundTaskExecutor is being loaded, instead of the customized version.
To clarify, we see that our custom executor is being registered, as the isolation level for remote promotions is at company level - this means that all promotions are being queued, independently of the site.
But we also see that the default executor is being used when the actual promotion happens, and not the custom one.
2) When restarting the portal, the original executor seems to be null, so apparently the BackgroundTaskExecutorRegistry customization needs to be enabled after the original one is loaded, so the original LayoutRemoteStagingBackgroundTaskExecutor instance is created.
Is there a better way to replace a background task executor?
Any advice you could offer would be greatly appreciated.
Thanks in advance,
Asier
In Liferay 7.0, we are trying to customize the LayoutRemoteStagingBackgroundTaskExecutor class., but we have not seen an extension point for it. It doesn't seem to be exported by its module, but it is being loaded through BackgroundTaskExecutorConfigurator, that creates an instance of the LayoutRemoteStagingBackgroundTaskExecutor, and it registers it as follows:
ServiceRegistration<BackgroundTaskExecutor> serviceRegistration =
bundleContext.registerService(
BackgroundTaskExecutor.class, backgroundTaskExecutor,
properties);
Using scr:info com.liferay.portal.background.task.internal.BackgroundTaskExecutorRegistryImpl, I see the reference:
Reference Properties:
background.task.executor.class.name = com.liferay.exportimport.background.task.LayoutRemoteStagingBackgroundTaskExecutor
objectClass = [com.liferay.portal.kernel.backgroundtask.BackgroundTaskExecutor]
service.bundleid = 623
service.id = 1106
service.scope = singleton
After several tries to replace the LayoutRemoteStagingBackgroundTaskExecutor by our custom one, we decided to replace the BackgroundTaskExecutorRegistryImpl to register the custom class.
We created the custom version with isolation level at company instead of group, and we see the correct isolation level in the logs and its outcome. We are loading the original executor with:
@Reference (
unbind = "-",
target = "(component.name=com.liferay.exportimport.background.task.LayoutRemoteStagingBackgroundTaskExecutor)"
)
protected BackgroundTaskExecutor _defaultExecutor;
And we use it when we want the default functionality.
However, this approach has two issues:
1) We added extra logs on the execute() method, and we are not seeing them. However, the background task is executed as expected, so we assume that at the time the execute() method is fired, the original LayoutRemoteStagingBackgroundTaskExecutor is being loaded, instead of the customized version.
To clarify, we see that our custom executor is being registered, as the isolation level for remote promotions is at company level - this means that all promotions are being queued, independently of the site.
But we also see that the default executor is being used when the actual promotion happens, and not the custom one.
2) When restarting the portal, the original executor seems to be null, so apparently the BackgroundTaskExecutorRegistry customization needs to be enabled after the original one is loaded, so the original LayoutRemoteStagingBackgroundTaskExecutor instance is created.
Is there a better way to replace a background task executor?
Any advice you could offer would be greatly appreciated.
Thanks in advance,
Asier
I'll give this one a shot.
Disclaimer. I have never done what you are describing here and while I THINK I have followed your explanation, I'm not confident I totally get what you have done - so likely many questions to follow
I'm answer though because I started having flashbacks to a 6.2 (so pre OSGI of course) days where I needed to modify classes that were part of a registry. In that case it has to do with the Social stuff, but I have the scenario where I had to override one class with my own version of it, and at runtime, a Registry was used to pull out the class I needed.
What we did, back then, as we used the *RegistryUtil class to unregister the one that was loaded by default, and then register our custom one. In your case I can see that there is a BackgriundTaskExecutorRegistryUtil which has --
so I am wondering if you can use it with something like this ...
This way you leave the registry impl as is, and you just swap out your implementation for the core one. The only thing I can't answer is what you said about sometimes wanting the functionality that comes with the LayoutRemoteStagingBackgroundTaskExecutor, but if you basically use the code from that class in your custom one and then make your alterations you would have the original logic available to you. Sucks for upgrade path mind you.
Thoughts?
Disclaimer. I have never done what you are describing here and while I THINK I have followed your explanation, I'm not confident I totally get what you have done - so likely many questions to follow

I'm answer though because I started having flashbacks to a 6.2 (so pre OSGI of course) days where I needed to modify classes that were part of a registry. In that case it has to do with the Social stuff, but I have the scenario where I had to override one class with my own version of it, and at runtime, a Registry was used to pull out the class I needed.
What we did, back then, as we used the *RegistryUtil class to unregister the one that was loaded by default, and then register our custom one. In your case I can see that there is a BackgriundTaskExecutorRegistryUtil which has --
public static void registerBackgroundTaskExecutor(
String backgroundTaskExecutorClassName,
BackgroundTaskExecutor backgroundTaskExecutor) {
_backgroundTaskExecutorRegistry.registerBackgroundTaskExecutor(
backgroundTaskExecutorClassName, backgroundTaskExecutor);
}
public static void unregisterBackgroundTaskExecutor(
String backgroundTaskExecutorClassName) {
_backgroundTaskExecutorRegistry.unregisterBackgroundTaskExecutor(
backgroundTaskExecutorClassName);
}
so I am wondering if you can use it with something like this ...
...
// unregister original one
BackgroundTaskExecutorRegistryUtil.unregister(BackgroundTaskExecutorNames.LAYOUT_REMOTE_STAGING_BACKGROUND_ASK_EXECUTOR);
// register custom one
BackgroundTaskExecutorRegistryUtil.register(BackgroundTaskExecutorNames.LAYOUT_REMOTE_STAGING_BACKGROUND_ASK_EXECUTOR, CustomExecutorYourWrote.class);
...
This way you leave the registry impl as is, and you just swap out your implementation for the core one. The only thing I can't answer is what you said about sometimes wanting the functionality that comes with the LayoutRemoteStagingBackgroundTaskExecutor, but if you basically use the code from that class in your custom one and then make your alterations you would have the original logic available to you. Sucks for upgrade path mind you.
Thoughts?
Andrew,
Thanks for the suggestion. It makes sense to me, I should be able to control it during the activation of the class, but not sure if it would work fine during the deactivation (if we want to go back to the default option). I will give it a try and update the post with the result.
Best,
Asier
Thanks for the suggestion. It makes sense to me, I should be able to control it during the activation of the class, but not sure if it would work fine during the deactivation (if we want to go back to the default option). I will give it a try and update the post with the result.
Best,
Asier
Right -- I would probably wrap this in its own module and use the Activate / Deactivate methods to run those items. To make sure that they core was in in place before swapping it out, in case you are not aware, you can use a dummy private member variable.
With this in place, the activation of the module (on startup) will be delayed until the portal has finished initializing at which point the stock implementation should be there.
If the activate/deactivate works the way you hope it will, then you get the added bonus of turning it on and off at run time using the gogo shell, or even writing a control panel module that will allow you to activate/deactivate it
@Reference(target = ModuleServiceLifecycle.[i][b]PORTAL_INITIALIZED[/b][/i])
private ModuleServiceLifecycle _moduleServiceLifecycle;
With this in place, the activation of the module (on startup) will be delayed until the portal has finished initializing at which point the stock implementation should be there.
If the activate/deactivate works the way you hope it will, then you get the added bonus of turning it on and off at run time using the gogo shell, or even writing a control panel module that will allow you to activate/deactivate it

Actually, I think this is a lot easier than you might think...
So LayoutRemoteStagingBackgroundTaskExecutor is not a component in 7.0, so it is not just a simple matter of creating your own and just registering it.
The construction and registration occurs in com.liferay.exportimport.background.task.BackgroundTaskExecutorConfigurator. This is a component, but it poses a challenge to try and replace because its package is not exported and the service registration is on that class.
If you were to, say, force exporting the package via https://liferay.dev/blogs/-/blogs/fixing-module-package-access-modifiers, you could extend the original and handle your own registration, but with so many other tasks being registered, that could be kind of messy.
You could try an OSGi-ish approach to try to track new services of this type and attempt to unregister it, but even this might interfere with your own registration effort.
How about this approach:
1. Create your own component that has the "background.task.executor.class.name=com.liferay.exportimport.background.task.LayoutRemoteStagingBackgroundTaskExecutor" property, but also give it a "service.ranking:Integer=200".
2. Include an @Reference(filter="(&(background.task.executor.class.name=com.liferay.exportimport.background.task.LayoutRemoteStagingBackgroundTaskExecutor)(component.name=com.liferay.exportimport.background.task.LayoutRemoteStagingBackgroundTaskExecutor))" for the BackgroundTaskExecutor. This will make your component dependent upon the Liferay background task, so your component won't start until Liferay's has started.
And that's it
Seriously, though, it is playing with how the BackgroundTaskRegistryImpl in 7.0 is tracking the services. As a BackgroundTaskExecutor is registered, the "background.task.executor.class.name" property is used as the key to a HashMap<String, BackgroundTaskExecutor>.
Since it is just a simple map, the last registered component w/ the same key will win.
And for OSGi future-proofing, adding the service.ranking property will mean that if the code switches to a ServiceTrackerMap implementation (as I see it is in 7.2, not sure about 7.1), your service should still trump the old.
So LayoutRemoteStagingBackgroundTaskExecutor is not a component in 7.0, so it is not just a simple matter of creating your own and just registering it.
The construction and registration occurs in com.liferay.exportimport.background.task.BackgroundTaskExecutorConfigurator. This is a component, but it poses a challenge to try and replace because its package is not exported and the service registration is on that class.
If you were to, say, force exporting the package via https://liferay.dev/blogs/-/blogs/fixing-module-package-access-modifiers, you could extend the original and handle your own registration, but with so many other tasks being registered, that could be kind of messy.
You could try an OSGi-ish approach to try to track new services of this type and attempt to unregister it, but even this might interfere with your own registration effort.
How about this approach:
1. Create your own component that has the "background.task.executor.class.name=com.liferay.exportimport.background.task.LayoutRemoteStagingBackgroundTaskExecutor" property, but also give it a "service.ranking:Integer=200".
2. Include an @Reference(filter="(&(background.task.executor.class.name=com.liferay.exportimport.background.task.LayoutRemoteStagingBackgroundTaskExecutor)(component.name=com.liferay.exportimport.background.task.LayoutRemoteStagingBackgroundTaskExecutor))" for the BackgroundTaskExecutor. This will make your component dependent upon the Liferay background task, so your component won't start until Liferay's has started.
And that's it

Seriously, though, it is playing with how the BackgroundTaskRegistryImpl in 7.0 is tracking the services. As a BackgroundTaskExecutor is registered, the "background.task.executor.class.name" property is used as the key to a HashMap<String, BackgroundTaskExecutor>.
Since it is just a simple map, the last registered component w/ the same key will win.
And for OSGi future-proofing, adding the service.ranking property will mean that if the code switches to a ServiceTrackerMap implementation (as I see it is in 7.2, not sure about 7.1), your service should still trump the old.
David,
Thanks for the input, I already tried creating my own component and it didn't work, as the LayoutRemoteStagingBackgroundTaskExecutor is internal. I will give it another try, just in case, and will let you know.
Andrew,
It was too nice to be good!! It didn't work because when I register my executor with the BackgroundTaskRegistryUtil, BackgroundTaskRegistryUtilImpl throws an exception in line 100.
Apparently, even if the method asks for a BackgroundTaskExecutor class, later the code is expecting a String. I am not positive but this looks like a bug to me.
The dummy service lifecicle initialization worked like a charm, and it was totally needed
.
Thoughts?
Best,
Asier
Thanks for the input, I already tried creating my own component and it didn't work, as the LayoutRemoteStagingBackgroundTaskExecutor is internal. I will give it another try, just in case, and will let you know.
Andrew,
It was too nice to be good!! It didn't work because when I register my executor with the BackgroundTaskRegistryUtil, BackgroundTaskRegistryUtilImpl throws an exception in line 100.
Apparently, even if the method asks for a BackgroundTaskExecutor class, later the code is expecting a String. I am not positive but this looks like a bug to me.
The dummy service lifecicle initialization worked like a charm, and it was totally needed

Thoughts?
Best,
Asier
Hey Asier,
Looking at the source, tht line 100 reference you made -- are you talking about this method?
.. because if you are, then I think that property that you are looking for is one of the ones that David referenced, in which case if you try his solution out it'll probably solved that issue for you because the component annotation would have the property (therefore no NPE -- assuming that is the excetion you are getting).
I know you replied to David and mentioned that you had tried his approach. but I think his steps were a little different, or at least they sounded different, then what you originally described. David is like a Liferay Oracle .. I'd be inclined to try again
Looking at the source, tht line 100 reference you made -- are you talking about this method?
protected synchronized void addBackgroundTaskExecutor(
BackgroundTaskExecutor backgroundTaskExecutor,
Map<string, object> properties) {
String backgroundTaskExecutorClassName = (String)properties.get(
"background.task.executor.class.name");
if (Validator.isNull(backgroundTaskExecutorClassName)) {
throw new IllegalArgumentException(
"Property \"background.task.executor.class.name\" is not set " +
"for " + backgroundTaskExecutor);
}
_backgroundTaskExecutors.put(
backgroundTaskExecutorClassName, backgroundTaskExecutor);
}</string,>
.. because if you are, then I think that property that you are looking for is one of the ones that David referenced, in which case if you try his solution out it'll probably solved that issue for you because the component annotation would have the property (therefore no NPE -- assuming that is the excetion you are getting).
I know you replied to David and mentioned that you had tried his approach. but I think his steps were a little different, or at least they sounded different, then what you originally described. David is like a Liferay Oracle .. I'd be inclined to try again

Asier Del Pozo:
Are you trying to extend the internal class in your implementation?
Thanks for the input, I already tried creating my own component and it didn't work, as the LayoutRemoteStagingBackgroundTaskExecutor is internal. I will give it another try, just in case, and will let you know.
In general, Liferay frowns on extending internal classes. In their view, packages that are not exported are that way for a reason. I think mostly from a support perspective, if a client extends an internal class and introduces a bug in the extension, but then they have an issue and report w/ stack traces pointing at Liferay code and support has no understanding that, in fact, there's an extension on the class that's misbehaving, it tends to be a support nightmare.
Me, I'm more on the "hey these things should be open" camp (I have the luxury of that position because I'm not in support). In that camp, we can use https://liferay.dev/blogs/-/blogs/fixing-module-package-access-modifiershttps://liferay.dev/blogs/-/blogs/fixing-module-package-access-modifiers to force the export of the package and allow for extension.
Of course, this goes with the obvious acknowledgement that is is not a Liferay supported practice to do this kind of thing, extension of Liferay classes in this way is not supported, it is up to you to be aware of possible impacts of new releases, fix packs, hot fixes, etc.
The Liferay-approved approach is for you to copy the class(es) outright into your own module and not have a specific dependency on a non-exported Liferay package or class. It is an approved path, but it also requires you to be aware of possible impacts of new releases, fix packs, etc. in that you may need to copy Liferay fixes back into your own copies.
Andrew Jardine:
are you talking about this method?
I am

Andrew Jardine:
I know you replied to David and mentioned that you had tried his approach. but I think his steps were a little different, or at least they sounded different, then what you originally described. David is like a Liferay Oracle .. I'd be inclined to try again
You are right that David is like a Liferay Oracle, I worked with him

I went ahead and tried that, but it didn't work, the custom class is ignored and the original one is always loaded.
I went back to your suggestion, but adding the property that David suggested, and it still fails. What I am getting is a Class Cast Exception, it seems that the properties map is created as a ReadOnlyDictionary, the background.task.executor.class.name key exists but it is either a char array or the actual class. It seems to me like that line is a bug, or at least doesn't do a good error control.
I modified the property to "background.task.executor.class.name=\"com.liferay.exportimport.background.task.LayoutRemoteStagingBackgroundTaskExecutor\"" and it does register the custom class, but it still fails, and it does not unregister the default one, as this call:
BackgroundTaskExecutorRegistryUtil.unregisterBackgroundTaskExecutor(BackgroundTaskExecutorNames.LAYOUT_REMOTE_STAGING_BACKGROUND_TASK_EXECUTOR);
Ends in the same Class Cast Exception. As the original class doesn't get unregistered because of the exception, I end with multiple original Liferay executors registered, which is a mess. It looks like a bug to me, or at least a bad error control; but I have not seen that method called anywhere in the source code.
David H Nebinger:
Are you trying to extend the internal class in your implementation?
No, I am trying to replace it (but you know, tomayto, tomahto). Thanks for the modifiers approach suggestion! I share your view on that it will be a maintenance nightmare, so it is my last resort, but I am running out of options.
Thanks!
Asier
Poop. Well, I can say that I have used David's approach where you expose internal classes before and it works like a charm. Sure there is maintenance on upgrades and fix packs, but he work is minimal, even if you do have to make changes. The biggest challenge I have found is REMEMBERING that I did it
-- but that was pretty easily solved by creating a wiki page with the upgrade steps (from Liferay) and adding your customizations to it as well. Sometimes the last resort is the only resort.

Copyright © 2025 Liferay, Inc
• Privacy Policy
Powered by Liferay™