Forums

Home » Liferay Portal » English » 3. Development

Combination View Flat View Tree View
Threads [ Previous | Next ]
toggle
Jacopo Bartolini
Disable OSGI component and use custom one
January 12, 2018 1:31 AM
Answer

Jacopo Bartolini

Rank: Junior Member

Posts: 26

Join Date: January 19, 2017

Recent Posts

Hi, I have developed a custom OSGI component, DLFileEntryActivityInterpreterHook, which changes some behavior of the DLFileEntryActivityInterpreter 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?

Here's my code for the custom component, which is only a class:
  1
  2@Component(
  3    immediate=true,
  4    property = {
  5            "javax.portlet.name=" + DLPortletKeys.DOCUMENT_LIBRARY,
  6            "service.ranking:Integer=100"
  7    },
  8    service = SocialActivityInterpreter.class
  9)
 10public class DLFileEntryActivityInterpreterHook
 11    extends BaseSocialActivityInterpreter {
 12
 13    @Override
 14    public String[] getClassNames() {
 15        return _CLASS_NAMES;
 16    }
 17   
 18    @Override
 19    protected String getLink(
 20            SocialActivity activity, ServiceContext serviceContext)
 21        throws Exception {
 22       
 23        String className = activity.getClassName();
 24        long classPK = activity.getClassPK();
 25
 26        String viewEntryInTrashURL = getViewEntryInTrashURL(
 27            className, classPK, serviceContext);
 28
 29        if (viewEntryInTrashURL != null) {
 30            return viewEntryInTrashURL;
 31        }
 32       
 33        try {
 34            PortletURL newURL = null;
 35            Layout layout = LayoutLocalServiceUtil.getLayout(
 36                        _portal.getPlidFromPortletId(
 37                            serviceContext.getScopeGroupId(), DLPortletKeys.DOCUMENT_LIBRARY));
 38            
 39            LayoutTypePortlet ltp = (LayoutTypePortlet) layout.getLayoutType();
 40            
 41            for(Portlet por : ltp.getPortlets()) {
 42                if(por.getPortletName().equals(DLPortletKeys.DOCUMENT_LIBRARY)) {
 43                   
 44                    newURL = PortletURLFactoryUtil.create(
 45                            serviceContext.getRequest(), por, layout, PortletRequest.RENDER_PHASE);
 46                   
 47                    newURL.setParameter("mvcRenderCommandName", "/document_library/view_file_entry");
 48                    newURL.setParameter("backURL", serviceContext.getCurrentURL());
 49                    newURL.setParameter("fileEntryId", String.valueOf(activity.getClassPK()));
 50                   
 51                    break;
 52                }
 53               
 54            }
 55            
 56            if(Validator.isNotNull(newURL)) {
 57                return newURL.toString();
 58            }
 59        } catch(Exception e) {
 60            _log.error(e);
 61        }
 62
 63        String path = getPath(activity, serviceContext);
 64
 65        if (Validator.isNull(path)) {
 66            return null;
 67        }
 68       
 69        path = addNoSuchEntryRedirect(path, className, classPK, serviceContext);
 70
 71        if (!path.startsWith(StringPool.SLASH)) {
 72            return path;
 73        }
 74       
 75        return serviceContext.getPortalURL() + serviceContext.getPathMain() +
 76            path;
 77    }
 78   
 79   
 80    @Override
 81    protected String getBody(
 82            SocialActivity activity, ServiceContext serviceContext)
 83        throws Exception {
 84
 85        FileEntry fileEntry = _dlAppLocalService.getFileEntry(
 86            activity.getClassPK());
 87
 88        if (TrashUtil.isInTrash(
 89                DLFileEntry.class.getName(), fileEntry.getFileEntryId())) {
 90
 91            return StringPool.BLANK;
 92        }
 93
 94        StringBundler sb = new StringBundler(3);
 95
 96        AssetRendererFactory<?> assetRendererFactory =
 97            AssetRendererFactoryRegistryUtil.getAssetRendererFactoryByClassName(
 98                DLFileEntry.class.getName());
 99
100        AssetRenderer<?> assetRenderer = assetRendererFactory.getAssetRenderer(
101            fileEntry.getFileEntryId());
102
103        String fileEntryLink = assetRenderer.getURLDownload(
104            serviceContext.getThemeDisplay());
105       
106        sb.append(wrapLinkHook(fileEntryLink, "download-file", serviceContext));
107
108        return sb.toString();
109    }
110
111    private String wrapLinkHook(String link, String key, ServiceContext serviceContext) {
112        ResourceBundle resourceBundle = getResourceBundleLoader().
113                loadResourceBundle(serviceContext.getLanguageId());
114
115        String title = HtmlUtil.escape(LanguageUtil.get(resourceBundle, key));
116
117        if (link == null) {
118            return title;
119        }
120
121        StringBundler sb = new StringBundler(5);
122
123        sb.append("<a href=\"");
124        sb.append(link);
125        sb.append("\">");
126        sb.append("<div class=\"btn btn-default btn-sm\">");
127        sb.append(title);
128        sb.append("</div></a>");
129
130        return sb.toString();
131    }
132
133    protected String getFolderLink(
134        FileEntry fileEntry, ServiceContext serviceContext) {
135
136        StringBundler sb = new StringBundler(6);
137
138        sb.append(serviceContext.getPortalURL());
139        sb.append(serviceContext.getPathMain());
140        sb.append("/document_library/find_folder?groupId=");
141        sb.append(fileEntry.getRepositoryId());
142        sb.append("&folderId=");
143        sb.append(fileEntry.getFolderId());
144
145        return sb.toString();
146    }
147
148    @Override
149    protected String getPath(
150        SocialActivity activity, ServiceContext serviceContext) {
151
152        return "/document_library/find_file_entry?fileEntryId=" +
153            activity.getClassPK();
154    }
155
156    @Override
157    protected ResourceBundleLoader getResourceBundleLoader() {
158        return _resourceBundleLoader;
159    }
160
161    @Override
162    protected Object[] getTitleArguments(
163            String groupName, SocialActivity activity, String link,
164            String title, ServiceContext serviceContext)
165        throws Exception {
166
167        if (activity.getType() == SocialActivityConstants.TYPE_ADD_COMMENT) {
168            String creatorUserName = getUserName(
169                activity.getUserId(), serviceContext);
170            String receiverUserName = getUserName(
171                activity.getReceiverUserId(), serviceContext);
172
173            return new Object[] {
174                groupName, creatorUserName, receiverUserName,
175                wrapLink(link, title)
176            };
177        }
178        else {
179            return super.getTitleArguments(
180                groupName, activity, link, title, serviceContext);
181        }
182    }
183
184    @Override
185    protected String getTitlePattern(
186        String groupName, SocialActivity activity) {
187
188        int activityType = activity.getType();
189
190        if (activityType == DLActivityKeys.ADD_FILE_ENTRY) {
191            if (Validator.isNull(groupName)) {
192                return "activity-document-library-file-add-file";
193            }
194            else {
195                return "activity-document-library-file-add-file-in";
196            }
197        }
198        else if (activityType == DLActivityKeys.UPDATE_FILE_ENTRY) {
199            if (Validator.isNull(groupName)) {
200                return "activity-document-library-file-update-file";
201            }
202            else {
203                return "activity-document-library-file-update-file-in";
204            }
205        }
206        else if (activityType == SocialActivityConstants.TYPE_ADD_COMMENT) {
207            if (Validator.isNull(groupName)) {
208                return "activity-document-library-file-add-comment";
209            }
210            else {
211                return "activity-document-library-file-add-comment-in";
212            }
213        }
214        else if (activityType == SocialActivityConstants.TYPE_MOVE_TO_TRASH) {
215            if (Validator.isNull(groupName)) {
216                return "activity-document-library-file-move-to-trash";
217            }
218            else {
219                return "activity-document-library-file-move-to-trash-in";
220            }
221        }
222        else if (activityType ==
223                    SocialActivityConstants.TYPE_RESTORE_FROM_TRASH) {
224
225            if (Validator.isNull(groupName)) {
226                return "activity-document-library-file-restore-from-trash";
227            }
228            else {
229                return "activity-document-library-file-restore-from-trash-in";
230            }
231        }
232
233        return null;
234    }
235
236    @Override
237    protected boolean hasPermissions(
238            PermissionChecker permissionChecker, SocialActivity activity,
239            String actionId, ServiceContext serviceContext)
240        throws Exception {
241
242        return DLFileEntryPermission.contains(
243            permissionChecker, activity.getClassPK(), actionId);
244    }
245
246    @Reference(unbind = "-")
247    protected void setDLAppLocalService(DLAppLocalService dlAppLocalService) {
248        _dlAppLocalService = dlAppLocalService;
249    }
250
251    @Reference(
252        target = "(bundle.symbolic.name=com.liferay.document.library.web)",
253        unbind = "-"
254    )
255    protected void setResourceBundleLoader(
256        ResourceBundleLoader resourceBundleLoader) {
257
258        _resourceBundleLoader = new AggregateResourceBundleLoader(
259            resourceBundleLoader,
260            ResourceBundleLoaderUtil.getPortalResourceBundleLoader());
261    }
262
263    @Reference
264    private Portal _portal;
265   
266    private static final String[] _CLASS_NAMES = {DLFileEntry.class.getName()};
267
268    private DLAppLocalService _dlAppLocalService;
269    private ResourceBundleLoader _resourceBundleLoader;
270   
271    private final static Log _log = LogFactoryUtil.getLog(DLFileEntryActivityInterpreterHook.class);
272
273}
David H Nebinger
RE: Disable OSGI component and use custom one
December 19, 2017 1:49 PM
Answer

David H Nebinger

Community Moderator

Rank: Liferay Legend

Posts: 13396

Join Date: September 1, 2006

Recent Posts

Service rankings don't work that way.

SocialActivityInterpreters are all collected using a ServiceTracker and each will be invoked as appropriate.

A service ranking only applies when you are trying to replace one service with another.

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.
Jacopo Bartolini
RE: Disable OSGI component and use custom one
January 9, 2018 8:27 AM
Answer

Jacopo Bartolini

Rank: Junior Member

Posts: 26

Join Date: January 19, 2017

Recent Posts

I'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?
David H Nebinger
RE: Disable OSGI component and use custom one
January 9, 2018 10:16 AM
Answer

David H Nebinger

Community Moderator

Rank: Liferay Legend

Posts: 13396

Join Date: September 1, 2006

Recent Posts

The 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.
Minhchau Dang
RE: Disable OSGI component and use custom one
January 11, 2018 9:30 AM
Answer

Minhchau Dang

LIFERAY STAFF

Rank: Expert

Posts: 415

Join Date: October 22, 2007

Recent Posts

Jacopo Bartolini:
Do you have a suggestion for me on how to reach my goal, to override one specific SocialActivityInterpreter with my own?

If you wish to disable the DLFileEntryActivityInterpreter, you could do so by calling the ServiceComponentRuntime when your custom component activates.

 1@Activate
 2public void activate(
 3        ComponentContext componentContext, BundleContext bundleContext,
 4        Map<String, Object> config)
 5    throws Exception {
 6
 7    String componentName = DLFileEntryActivityInterpreter.class.getName();
 8
 9    Collection<ServiceReference<SocialActivityInterpreter>>
10        serviceReferences =
11            bundleContext.getServiceReferences(
12                SocialActivityInterpreter.class,
13                "(component.name=" + componentName + ")");
14
15    for (ServiceReference serviceReference : serviceReferences) {
16        Bundle bundle = serviceReference.getBundle();
17
18        ComponentDescriptionDTO description =
19            _serviceComponentRuntime.getComponentDescriptionDTO(
20                bundle, componentName);
21
22        _serviceComponentRuntime.disableComponent(description);
23    }
24}
25
26@Reference
27private ServiceComponentRuntime _serviceComponentRuntime;

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.
Jacopo Bartolini
RE: Disable OSGI component and use custom one
January 15, 2018 1:26 AM
Answer

Jacopo Bartolini

Rank: Junior Member

Posts: 26

Join Date: January 19, 2017

Recent Posts

Thank you very much Minchau, that worked like a charm.
And also thank you David too.
Jacopo Bartolini
RE: Disable OSGI component and use custom one
February 2, 2018 2:42 AM
Answer

Jacopo Bartolini

Rank: Junior Member

Posts: 26

Join Date: January 19, 2017

Recent Posts

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).
Here's the code i wrote for the activate() method, which is pretty much the same you wrote:
 1
 2       @Activate
 3    public void activate(
 4            ComponentContext componentContext, BundleContext bundleContext,
 5            Map<String, Object> config)
 6        throws Exception {
 7       
 8        String componentName = "com.liferay.product.navigation.product.menu.web.internal.product.navigation.control.menu.ProductMenuProductNavigationControlMenuEntry";
 9       
10        Collection<ServiceReference<ProductNavigationControlMenuEntry>> serviceReferences;
11        int i = 0;
12       
13        do {
14       
15            _log.info("Attempt number " + (i+1));
16       
17            serviceReferences = bundleContext.getServiceReferences(ProductNavigationControlMenuEntry.class,
18                "(component.name=" + componentName + ")");
19       
20            i++;
21       
22            Thread.sleep(1000);
23       
24        } while(serviceReferences.isEmpty() && i<5);
25       
26        if(i==5 && serviceReferences.isEmpty()) {
27            _log.warn("No service reference found");
28        }
29       
30        for(ServiceReference serviceReference : serviceReferences) {
31            Bundle bundle = serviceReference.getBundle();
32            
33            ComponentDescriptionDTO description =
34                _serviceComponentRuntime.getComponentDescriptionDTO(bundle, componentName);
35            
36            _serviceComponentRuntime.disableComponent(description);
37        }
38    }


If there is any advice that you could give me, It would be really helpful. Thanks in advance.
Attachment

Attachment

Attachments: beep1.png (625.9k), beep2.png (464.7k)
David H Nebinger
RE: Disable OSGI component and use custom one
February 2, 2018 6:59 AM
Answer

David H Nebinger

Community Moderator

Rank: Liferay Legend

Posts: 13396

Join Date: September 1, 2006

Recent Posts

Jacopo Bartolini:
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).


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.

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.
Jacopo Bartolini
RE: Disable OSGI component and use custom one
February 2, 2018 7:39 AM
Answer

Jacopo Bartolini

Rank: Junior Member

Posts: 26

Join Date: January 19, 2017

Recent Posts

Thank you David, that's a good advice. I'll try it Monday morning, hopefully it will work.
Bye, and thanks again.
Jacopo Bartolini
RE: Disable OSGI component and use custom one
February 16, 2018 3:06 AM
Answer

Jacopo Bartolini

Rank: Junior Member

Posts: 26

Join Date: January 19, 2017

Recent Posts

Hi David,
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.

That's one of the most annoying problem I've faced: modules are so easy to replace, while components are the exact some opposite.
Do you have any other suggestion?

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.

Here I include the code for the custom module I've created:
 1
 2
 3@Component(
 4        immediate = true,
 5        property = {"key=application.startup.events"},
 6        service = LifecycleAction.class
 7)
 8public class CustomComponentStarter implements LifecycleAction {
 9
10   
11    @Override
12    public void processLifecycleEvent(LifecycleEvent lifecycleEvent) throws ActionException {
13       
14        _log.info("Starting...");
15       
16        Bundle myBundle = FrameworkUtil.
17                getBundle(CustomComponentStarter.class);
18       
19        BundleContext bundleContext = myBundle.getBundleContext();
20       
21        String componentNames[] = new String[] {
22                DLFileEntryActivityInterpreter.class.getName(), MBMessageActivityInterpreter.class.getName()};
23        for(String componentName : componentNames) {
24            try {
25                Collection<ServiceReference<SocialActivityInterpreter>> serviceReferences =
26                        bundleContext.getServiceReferences(SocialActivityInterpreter.class,
27                                "(component.name=" + componentName + ")");
28               
29                if(serviceReferences.isEmpty()) {
30                    _log.info("No serviceReferences found for component " + componentName);
31                }
32               
33                for(ServiceReference serviceReference : serviceReferences) {
34                    Bundle bundle = serviceReference.getBundle();
35                   
36                    ComponentDescriptionDTO description =
37                        _serviceComponentRuntime.getComponentDescriptionDTO(bundle, componentName);
38                   
39                    _serviceComponentRuntime.disableComponent(description);
40                   
41                    _log.info("Disabled component " + componentName);
42                }
43            } catch (InvalidSyntaxException ise) {
44                _log.error(ise);   
45            }
46        }
47       
48        try {
49            String productNavigationComponent = "com.liferay.product.navigation.product.menu.web.internal.product.navigation.control.menu.ProductMenuProductNavigationControlMenuEntry";
50            
51            Collection<ServiceReference<ProductNavigationControlMenuEntry>> serviceReferences =
52                    bundleContext.getServiceReferences(ProductNavigationControlMenuEntry.class,
53                            "(component.name=" + productNavigationComponent + ")");
54            
55            if(serviceReferences.isEmpty()) {
56                _log.info("No serviceReferences found for component " + productNavigationComponent);
57            }
58            
59            for(ServiceReference serviceReference : serviceReferences) {
60                Bundle bundle = serviceReference.getBundle();
61               
62                ComponentDescriptionDTO description =
63                    _serviceComponentRuntime.getComponentDescriptionDTO(bundle, productNavigationComponent);
64               
65                _serviceComponentRuntime.disableComponent(description);
66               
67                _log.info("Disabled component " + productNavigationComponent);
68            }
69        } catch (InvalidSyntaxException ise) {
70            _log.error(ise);
71        }
72    }
73   
74    @Reference
75    private ServiceComponentRuntime _serviceComponentRuntime;
76   
77    private final static Log _log = LogFactoryUtil.getLog(CustomComponentStarter.class);
78}
Minhchau Dang
RE: Disable OSGI component and use custom one
February 20, 2018 1:08 PM
Answer

Minhchau Dang

LIFERAY STAFF

Rank: Expert

Posts: 415

Join Date: October 22, 2007

Recent Posts

Jacopo Bartolini:
Sometimes this module is able to find and disable the Liferay default components, sometimes it's not able to do it.

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.

Edit: 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).

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).

 1@Activate
 2public void activate(
 3        ComponentContext componentContext, BundleContext bundleContext,
 4        Map<String, Object> config)
 5    throws Exception {
 6
 7    _bundleContext = bundleContext;
 8
 9    deactivateExistingComponent();
10}
11
12@Reference(
13    cardinality = ReferenceCardinality.OPTIONAL,
14    policy = ReferencePolicy.DYNAMIC,
15    policyOption = ReferencePolicyOption.GREEDY
16)
17public void setProductNavigationControlMenuEntry(
18    ProductNavigationControlMenuEntry productNavigationControlMenuEntry) {
19   
20    deactivateExistingComponent();
21}
22
23public void deactivateExistingComponent() {
24    if (_bundleContext == null) {
25        return;
26    }
27
28    // All the logic from before
29}

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.

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.

Participate in the State of Liferay Community 2017. Help the community and even win some prizes!