Disable OSGI component and use custom oneDisable OSGI component and use custom onehttps://liferay.dev/en/c/message_boards/find_thread?p_l_id=119785333&threadId=1011935842024-03-28T22:47:24Z2024-03-28T22:47:24ZRE: Disable OSGI component and use custom oneHenrique Andradehttps://liferay.dev/en/c/message_boards/find_message?p_l_id=119785333&messageId=1081817512018-05-12T12:07:08Z2018-05-12T12:07:08Z<html><head></head><body><div class="quote-title">Minhchau Dang:</div><blockquote><div class="quote-title">Jacopo Bartolini:</div><blockquote>Sometimes this module is able to find and disable the Liferay default components, sometimes it's not able to do it.</blockquote><br>Have you tried depending on the component you're trying to replace by adding a @Reference to it? That will delay the call to the @Activate annotated method until after the other component (that you're trying to disable) is available.<br><br><strong>Edit</strong>: Actually, I spoke too soon. Delaying the activation won't work, because as soon as you disable your dependency, it will probably disable your component as well (because your dependency is no longer satisfied).<br><br>Rather, you'll want to make it an optional @Reference, and go ahead and call the method both on component activation and if the reference is satisfied, but only do something if the component has been activated (in other words, the BundleContext you need is not null).<br><br><pre><code>@Activate
public void activate(
ComponentContext componentContext, BundleContext bundleContext,
Map<string, object> config)
throws Exception {
_bundleContext = bundleContext;
deactivateExistingComponent();
}
@Reference(
cardinality = ReferenceCardinality.OPTIONAL,
policy = ReferencePolicy.DYNAMIC,
policyOption = ReferencePolicyOption.GREEDY
)
public void setProductNavigationControlMenuEntry(
ProductNavigationControlMenuEntry productNavigationControlMenuEntry) {
deactivateExistingComponent();
}
public void deactivateExistingComponent() {
if (_bundleContext == null) {
return;
}
// All the logic from before
}
</string,></code></pre><br>The concept itself is usually tricky if you only want the method to ever run once, because your own custom component is providing that same interface (so you may want to limit the target to have an objectClass that's just the class you're overriding, so that it doesn't match your component), but component deactivation is something that you should be able to run multiple times.<br><br>At compile time, bnd.bnd might analyze the class and demand an unbind method, which you can provide by adding an empty method named unsetProductNavigationControlMenuEntry that takes in the same parameter types as the setProductNavigationControlMenuEntry method.</blockquote><br>Hello!<br><br>I had same thread behavior, and tried this approach to 'replace' another portal component with my custom one, and worked very well even after portal restart.<br><br>Thanks!</body></html>Henrique Andrade2018-05-12T12:07:08ZRE: Disable OSGI component and use custom oneMinhchau Danghttps://liferay.dev/en/c/message_boards/find_message?p_l_id=119785333&messageId=1042789792018-02-20T20:50:24Z2018-02-20T20:50:24Z<html><head></head><body><div class="quote-title">Jacopo Bartolini:</div><blockquote>Sometimes this module is able to find and disable the Liferay default components, sometimes it's not able to do it.</blockquote><br>Have you tried depending on the component you're trying to replace by adding a @Reference to it? That will delay the call to the @Activate annotated method until after the other component (that you're trying to disable) is available.<br><br><strong>Edit</strong>: Actually, I spoke too soon. Delaying the activation won't work, because as soon as you disable your dependency, it will probably disable your component as well (because your dependency is no longer satisfied).<br><br>Rather, you'll want to make it an optional @Reference, and go ahead and call the method both on component activation and if the reference is satisfied, but only do something if the component has been activated (in other words, the BundleContext you need is not null).<br><br><pre><code>@Activate
public void activate(
ComponentContext componentContext, BundleContext bundleContext,
Map<string, object> config)
throws Exception {
_bundleContext = bundleContext;
deactivateExistingComponent();
}
@Reference(
cardinality = ReferenceCardinality.OPTIONAL,
policy = ReferencePolicy.DYNAMIC,
policyOption = ReferencePolicyOption.GREEDY
)
public void setProductNavigationControlMenuEntry(
ProductNavigationControlMenuEntry productNavigationControlMenuEntry) {
deactivateExistingComponent();
}
public void deactivateExistingComponent() {
if (_bundleContext == null) {
return;
}
// All the logic from before
}
</string,></code></pre><br>The concept itself is usually tricky if you only want the method to ever run once, because your own custom component is providing that same interface (so you may want to limit the target to have an objectClass that's just the class you're overriding, so that it doesn't match your component), but component deactivation is something that you should be able to run multiple times.<br><br>At compile time, bnd.bnd might analyze the class and demand an unbind method, which you can provide by adding an empty method named unsetProductNavigationControlMenuEntry that takes in the same parameter types as the setProductNavigationControlMenuEntry method.</body></html>Minhchau Dang2018-02-20T20:50:24ZRE: Disable OSGI component and use custom oneJacopo Bartolinihttps://liferay.dev/en/c/message_boards/find_message?p_l_id=119785333&messageId=1041040262018-02-16T11:01:07Z2018-02-16T11:01:07Z<html><head></head><body>Hi David,<br>I've tried following your advice. I've created a class which implements LifecycleAction. In this class I've replicated the code that Minhchau Dang posted here before, but I've encountered the exact same problem I was facing before: sometimes this module is able to find and disable the Liferay default components, sometimes it's not able to do it. I've tried to bind this module either to the application.startup.events or to the global.startup.events, but none of it seems to work.<br><br>That's one of the most annoying problem I've faced: modules are so easy to replace, while components are the exact some opposite.<br>Do you have any other suggestion?<br><br>I was also wondering if it would be better to just replace the entire Liferay module with a custom one, but I'm not sure if that is an overkill or not (and also, I'm not sure if that would work). Let me know your thoughts on this, please.<br><br>Here I include the code for the custom module I've created:<br><pre><code>
@Component(
immediate = true,
property = {"key=application.startup.events"},
service = LifecycleAction.class
)
public class CustomComponentStarter implements LifecycleAction {
@Override
public void processLifecycleEvent(LifecycleEvent lifecycleEvent) throws ActionException {
_log.info("Starting...");
Bundle myBundle = FrameworkUtil.
getBundle(CustomComponentStarter.class);
BundleContext bundleContext = myBundle.getBundleContext();
String componentNames[] = new String[] {
DLFileEntryActivityInterpreter.class.getName(), MBMessageActivityInterpreter.class.getName()};
for(String componentName : componentNames) {
try {
Collection<servicereference<socialactivityinterpreter>&gt; serviceReferences =
bundleContext.getServiceReferences(SocialActivityInterpreter.class,
"(component.name=" + componentName + ")");
if(serviceReferences.isEmpty()) {
_log.info("No serviceReferences found for component " + componentName);
}
for(ServiceReference serviceReference : serviceReferences) {
Bundle bundle = serviceReference.getBundle();
ComponentDescriptionDTO description =
_serviceComponentRuntime.getComponentDescriptionDTO(bundle, componentName);
_serviceComponentRuntime.disableComponent(description);
_log.info("Disabled component " + componentName);
}
} catch (InvalidSyntaxException ise) {
_log.error(ise);
}
}
try {
String productNavigationComponent = "com.liferay.product.navigation.product.menu.web.internal.product.navigation.control.menu.ProductMenuProductNavigationControlMenuEntry";
Collection<servicereference<productnavigationcontrolmenuentry>&gt; serviceReferences =
bundleContext.getServiceReferences(ProductNavigationControlMenuEntry.class,
"(component.name=" + productNavigationComponent + ")");
if(serviceReferences.isEmpty()) {
_log.info("No serviceReferences found for component " + productNavigationComponent);
}
for(ServiceReference serviceReference : serviceReferences) {
Bundle bundle = serviceReference.getBundle();
ComponentDescriptionDTO description =
_serviceComponentRuntime.getComponentDescriptionDTO(bundle, productNavigationComponent);
_serviceComponentRuntime.disableComponent(description);
_log.info("Disabled component " + productNavigationComponent);
}
} catch (InvalidSyntaxException ise) {
_log.error(ise);
}
}
@Reference
private ServiceComponentRuntime _serviceComponentRuntime;
private final static Log _log = LogFactoryUtil.getLog(CustomComponentStarter.class);
}
</servicereference<productnavigationcontrolmenuentry></servicereference<socialactivityinterpreter></code></pre></body></html>Jacopo Bartolini2018-02-16T11:01:07ZRE: Disable OSGI component and use custom oneJacopo Bartolinihttps://liferay.dev/en/c/message_boards/find_message?p_l_id=119785333&messageId=1034727702018-02-02T15:39:42Z2018-02-02T15:39:42ZThank you David, that's a good advice. I'll try it Monday morning, hopefully it will work. <br />Bye, and thanks again.Jacopo Bartolini2018-02-02T15:39:42ZRE: Disable OSGI component and use custom oneDavid H Nebingerhttps://liferay.dev/en/c/message_boards/find_message?p_l_id=119785333&messageId=1034718372018-02-02T14:59:42Z2018-02-02T14:59:42Z<div class="quote-title">Jacopo Bartolini:</div><blockquote>I'm sorry to bother you again, but I've encountered a problem: when i restart the app server, sometimes my custom component fails on finding the default one, so that now I'm back at having two components running (and two product menu icons).</blockquote><br /><br />That's the nature of the code. By using the activate method to look for a current implementation, at startup time (when you have no control over bundle load order) your code is running and not finding one (because it hasn't started yet) so there is nothing for it to remove. Sometime thereafter, the other starts and you're left with two instances.<br /><br />I would probably bind your component to a portal lifecycle start, that way you delay the run of your activate until the portal itself has completed startup. Then it should always find the old one and can kill it.David H Nebinger2018-02-02T14:59:42ZRE: Disable OSGI component and use custom oneJacopo Bartolinihttps://liferay.dev/en/c/message_boards/find_message?p_l_id=119785333&messageId=1034599262018-02-02T10:35:56Z2018-02-02T10:35:56Z<html><head></head><body>I'm sorry to bother you again, but I've encountered a problem: when i restart the app server, sometimes my custom component fails on finding the default one, so that now I'm back at having two components running (and two product menu icons). While looking at the logs, I can clearly see that my component is activated (second picture) after the bundle containing the default component (first picture). <br>Here's the code i wrote for the activate() method, which is pretty much the same you wrote:<br><pre><code>
@Activate
public void activate(
ComponentContext componentContext, BundleContext bundleContext,
Map<string, object> config)
throws Exception {
String componentName = "com.liferay.product.navigation.product.menu.web.internal.product.navigation.control.menu.ProductMenuProductNavigationControlMenuEntry";
Collection<servicereference<productnavigationcontrolmenuentry>&gt; serviceReferences;
int i = 0;
do {
_log.info("Attempt number " + (i+1));
serviceReferences = bundleContext.getServiceReferences(ProductNavigationControlMenuEntry.class,
"(component.name=" + componentName + ")");
i++;
Thread.sleep(1000);
} while(serviceReferences.isEmpty() &amp;&amp; i&lt;5);
if(i==5 &amp;&amp; serviceReferences.isEmpty()) {
_log.warn("No service reference found");
}
for(ServiceReference serviceReference : serviceReferences) {
Bundle bundle = serviceReference.getBundle();
ComponentDescriptionDTO description =
_serviceComponentRuntime.getComponentDescriptionDTO(bundle, componentName);
_serviceComponentRuntime.disableComponent(description);
}
}
</servicereference<productnavigationcontrolmenuentry></string,></code></pre><br><br>If there is any advice that you could give me, It would be really helpful. Thanks in advance.</body></html>Jacopo Bartolini2018-02-02T10:35:56ZRE: Disable OSGI component and use custom oneJacopo Bartolinihttps://liferay.dev/en/c/message_boards/find_message?p_l_id=119785333&messageId=1027631882018-01-15T09:26:16Z2018-01-15T09:26:16ZThank you very much Minchau, that worked like a charm.<br />And also thank you David too.Jacopo Bartolini2018-01-15T09:26:16ZRE: Disable OSGI component and use custom oneMinhchau Danghttps://liferay.dev/en/c/message_boards/find_message?p_l_id=119785333&messageId=1026493012018-01-11T17:24:47Z2018-01-11T17:24:47Z<html><head></head><body><div class="quote-title">Jacopo Bartolini:</div><blockquote>Do you have a suggestion for me on how to reach my goal, to override one specific SocialActivityInterpreter with my own?</blockquote><br>If you wish to disable the <a href="https://github.com/liferay/liferay-portal/blob/7.0.4-ga5/modules/apps/collaboration/document-library/document-library-web/src/main/java/com/liferay/document/library/web/social/DLFileEntryActivityInterpreter.java">DLFileEntryActivityInterpreter</a>, you could do so by calling the <a href="http://javadox.com/org.osgi/osgi.cmpn/6.0.0/org/osgi/service/component/runtime/ServiceComponentRuntime.html">ServiceComponentRuntime</a> when your custom component activates.<br><br><pre><code>@Activate
public void activate(
ComponentContext componentContext, BundleContext bundleContext,
Map<string, object> config)
throws Exception {
String componentName = DLFileEntryActivityInterpreter.class.getName();
Collection<servicereference<socialactivityinterpreter>&gt;
serviceReferences =
bundleContext.getServiceReferences(
SocialActivityInterpreter.class,
"(component.name=" + componentName + ")");
for (ServiceReference serviceReference : serviceReferences) {
Bundle bundle = serviceReference.getBundle();
ComponentDescriptionDTO description =
_serviceComponentRuntime.getComponentDescriptionDTO(
bundle, componentName);
_serviceComponentRuntime.disableComponent(description);
}
}
@Reference
private ServiceComponentRuntime _serviceComponentRuntime;
</servicereference<socialactivityinterpreter></string,></code></pre><br>If for some reason you wish to reactivate the original component once your component has deactivated, you could use similar code when your custom component deactivates, but call the enableComponent method instead.</body></html>Minhchau Dang2018-01-11T17:24:47ZRE: Disable OSGI component and use custom oneDavid H Nebingerhttps://liferay.dev/en/c/message_boards/find_message?p_l_id=119785333&messageId=1024739742018-01-09T18:16:14Z2018-01-09T18:16:14ZThe only way you can is to replace the original file. This could involve doing a "module extending a module" thing I blogged about previously if you're trying to replace a Liferay interpreter.David H Nebinger2018-01-09T18:16:14ZRE: Disable OSGI component and use custom oneJacopo Bartolinihttps://liferay.dev/en/c/message_boards/find_message?p_l_id=119785333&messageId=1024651462018-01-09T16:27:11Z2018-01-09T16:27:11ZI'm sorry for my late response, holidays got me. First of all, thanks for your response. So, I understand what you're saying David. Do you have a suggestion for me on how to reach my goal, to override one specific SocialActivityInterpreter with my own?Jacopo Bartolini2018-01-09T16:27:11ZRE: Disable OSGI component and use custom oneDavid H Nebingerhttps://liferay.dev/en/c/message_boards/find_message?p_l_id=119785333&messageId=1012506472017-12-19T21:49:24Z2017-12-19T21:49:24ZService rankings don't work that way.<br /><br />SocialActivityInterpreters are all collected using a ServiceTracker and each will be invoked as appropriate.<br /><br />A service ranking only applies when you are trying to replace one service with another.<br /><br />In this current case, the service tracker will not know that you are actually trying to replace one SAI with another, from its perspective you are just adding an additional SAI.David H Nebinger2017-12-19T21:49:24ZDisable OSGI component and use custom oneJacopo Bartolinihttps://liferay.dev/en/c/message_boards/find_message?p_l_id=119785333&messageId=1011935832017-12-19T13:23:35Z2017-12-19T13:23:35Z<html><head></head><body>Hi, I have developed a custom OSGI component, DLFileEntryActivityInterpreterHook, which changes some behavior of the <a href="https://github.com/liferay/liferay-portal/blob/7.0.4-ga5/modules/apps/collaboration/document-library/document-library-web/src/main/java/com/liferay/document/library/web/social/DLFileEntryActivityInterpreter.java">DLFileEntryActivityInterpreter</a> component. When I deploy it and use the command scr:disable from the GoGo shell, everything works fine. But when I restart the portal, the default component kicks in and I have to disable it from the GoGo shell again. What should I do?<br><br>Here's my code for the custom component, which is only a class:<br><pre><code>
@Component(
immediate=true,
property = {
"javax.portlet.name=" + DLPortletKeys.DOCUMENT_LIBRARY,
"service.ranking:Integer=100"
},
service = SocialActivityInterpreter.class
)
public class DLFileEntryActivityInterpreterHook
extends BaseSocialActivityInterpreter {
@Override
public String[] getClassNames() {
return _CLASS_NAMES;
}
@Override
protected String getLink(
SocialActivity activity, ServiceContext serviceContext)
throws Exception {
String className = activity.getClassName();
long classPK = activity.getClassPK();
String viewEntryInTrashURL = getViewEntryInTrashURL(
className, classPK, serviceContext);
if (viewEntryInTrashURL != null) {
return viewEntryInTrashURL;
}
try {
PortletURL newURL = null;
Layout layout = LayoutLocalServiceUtil.getLayout(
_portal.getPlidFromPortletId(
serviceContext.getScopeGroupId(), DLPortletKeys.DOCUMENT_LIBRARY));
LayoutTypePortlet ltp = (LayoutTypePortlet) layout.getLayoutType();
for(Portlet por : ltp.getPortlets()) {
if(por.getPortletName().equals(DLPortletKeys.DOCUMENT_LIBRARY)) {
newURL = PortletURLFactoryUtil.create(
serviceContext.getRequest(), por, layout, PortletRequest.RENDER_PHASE);
newURL.setParameter("mvcRenderCommandName", "/document_library/view_file_entry");
newURL.setParameter("backURL", serviceContext.getCurrentURL());
newURL.setParameter("fileEntryId", String.valueOf(activity.getClassPK()));
break;
}
}
if(Validator.isNotNull(newURL)) {
return newURL.toString();
}
} catch(Exception e) {
_log.error(e);
}
String path = getPath(activity, serviceContext);
if (Validator.isNull(path)) {
return null;
}
path = addNoSuchEntryRedirect(path, className, classPK, serviceContext);
if (!path.startsWith(StringPool.SLASH)) {
return path;
}
return serviceContext.getPortalURL() + serviceContext.getPathMain() +
path;
}
@Override
protected String getBody(
SocialActivity activity, ServiceContext serviceContext)
throws Exception {
FileEntry fileEntry = _dlAppLocalService.getFileEntry(
activity.getClassPK());
if (TrashUtil.isInTrash(
DLFileEntry.class.getName(), fileEntry.getFileEntryId())) {
return StringPool.BLANK;
}
StringBundler sb = new StringBundler(3);
AssetRendererFactory<!--?--> assetRendererFactory =
AssetRendererFactoryRegistryUtil.getAssetRendererFactoryByClassName(
DLFileEntry.class.getName());
AssetRenderer<!--?--> assetRenderer = assetRendererFactory.getAssetRenderer(
fileEntry.getFileEntryId());
String fileEntryLink = assetRenderer.getURLDownload(
serviceContext.getThemeDisplay());
sb.append(wrapLinkHook(fileEntryLink, "download-file", serviceContext));
return sb.toString();
}
private String wrapLinkHook(String link, String key, ServiceContext serviceContext) {
ResourceBundle resourceBundle = getResourceBundleLoader().
loadResourceBundle(serviceContext.getLanguageId());
String title = HtmlUtil.escape(LanguageUtil.get(resourceBundle, key));
if (link == null) {
return title;
}
StringBundler sb = new StringBundler(5);
sb.append("<a href="\&quot;&quot;);" sb.append(link); sb.append("\">");
sb.append("<div class="\&quot;btn" btn-default btn-sm\">");
sb.append(title);
sb.append("</div></a>");
return sb.toString();
}
protected String getFolderLink(
FileEntry fileEntry, ServiceContext serviceContext) {
StringBundler sb = new StringBundler(6);
sb.append(serviceContext.getPortalURL());
sb.append(serviceContext.getPathMain());
sb.append("/document_library/find_folder?groupId=");
sb.append(fileEntry.getRepositoryId());
sb.append("&amp;folderId=");
sb.append(fileEntry.getFolderId());
return sb.toString();
}
@Override
protected String getPath(
SocialActivity activity, ServiceContext serviceContext) {
return "/document_library/find_file_entry?fileEntryId=" +
activity.getClassPK();
}
@Override
protected ResourceBundleLoader getResourceBundleLoader() {
return _resourceBundleLoader;
}
@Override
protected Object[] getTitleArguments(
String groupName, SocialActivity activity, String link,
String title, ServiceContext serviceContext)
throws Exception {
if (activity.getType() == SocialActivityConstants.TYPE_ADD_COMMENT) {
String creatorUserName = getUserName(
activity.getUserId(), serviceContext);
String receiverUserName = getUserName(
activity.getReceiverUserId(), serviceContext);
return new Object[] {
groupName, creatorUserName, receiverUserName,
wrapLink(link, title)
};
}
else {
return super.getTitleArguments(
groupName, activity, link, title, serviceContext);
}
}
@Override
protected String getTitlePattern(
String groupName, SocialActivity activity) {
int activityType = activity.getType();
if (activityType == DLActivityKeys.ADD_FILE_ENTRY) {
if (Validator.isNull(groupName)) {
return "activity-document-library-file-add-file";
}
else {
return "activity-document-library-file-add-file-in";
}
}
else if (activityType == DLActivityKeys.UPDATE_FILE_ENTRY) {
if (Validator.isNull(groupName)) {
return "activity-document-library-file-update-file";
}
else {
return "activity-document-library-file-update-file-in";
}
}
else if (activityType == SocialActivityConstants.TYPE_ADD_COMMENT) {
if (Validator.isNull(groupName)) {
return "activity-document-library-file-add-comment";
}
else {
return "activity-document-library-file-add-comment-in";
}
}
else if (activityType == SocialActivityConstants.TYPE_MOVE_TO_TRASH) {
if (Validator.isNull(groupName)) {
return "activity-document-library-file-move-to-trash";
}
else {
return "activity-document-library-file-move-to-trash-in";
}
}
else if (activityType ==
SocialActivityConstants.TYPE_RESTORE_FROM_TRASH) {
if (Validator.isNull(groupName)) {
return "activity-document-library-file-restore-from-trash";
}
else {
return "activity-document-library-file-restore-from-trash-in";
}
}
return null;
}
@Override
protected boolean hasPermissions(
PermissionChecker permissionChecker, SocialActivity activity,
String actionId, ServiceContext serviceContext)
throws Exception {
return DLFileEntryPermission.contains(
permissionChecker, activity.getClassPK(), actionId);
}
@Reference(unbind = "-")
protected void setDLAppLocalService(DLAppLocalService dlAppLocalService) {
_dlAppLocalService = dlAppLocalService;
}