BundleActivator implementation and OSGi injection

Ketan Solanki, modified 7 Years ago. Junior Member Posts: 63 Join Date: 5/28/14 Recent Posts

Hi All,

 

I want to execute a piece of code every time a module is deployed! So I searched forums and I got following 2 useful APIs

 

com.liferay.portal.kernel.events.SimpleAction --> This gets activated ONLY while Liferay server is starting (here)

org.osgi.framework.BundleActivator --> This gets activated every time the module is deployed, so this fits (here)

If I put the LOG statements in the BundleActivator implementation, it gets printed!

 

But my primary purpose is to invoke a method of some other Component class from this start method. 

@Component(immediate = true, service = StartupActivator.class)
public class StartupActivator implements BundleActivator {

@Reference
private LiferayInstanceService liferayInstanceService;

private static final Log LOG = LogFactoryUtil.getLog(StartupActivator.class);

@Override
public void start(BundleContext context) throws Exception {
    LOG.info("I am running NOW");
    this.liferayInstanceService.setSiteCustomField("RefreshJson", Boolean.TRUE);
}

Above code in start() method basically sets the "RefreshJson" site custom field to TRUE everytime this module is deployed! But I am not getting instance of  liferayInstanceService injected into the class (this injection works fine in all other component classes throughout the project)! This class definition is as below...

@Component(immediate = true, service = LiferayInstanceService.class)
public class LiferayInstanceService {

So I guess BundleActivator class is invoked before OSGi initializes everything else or may be something else... Is there any way to get this instance of the class successfully injected into this? Please let me know this or any other way to do so...

Thanks,

Ketan

thumbnail
Christoph Rabel, modified 7 Years ago. Liferay Legend Posts: 1555 Join Date: 9/24/09 Recent Posts

Not sure if this will help you, but adding this reference here tells your module to wait, till the portal is initialized.

      @Reference(target = ModuleServiceLifecycle.PORTAL_INITIALIZED, unbind = "-")
      protected void setModuleServiceLifecycle(ModuleServiceLifecycle moduleServiceLifecycle) {
      }

Please note, that there is something fishy here, I don't think that this will resolve the root problem. Are you sure that you don't get a reference to LiferayInstanceService?

thumbnail
Olaf Kock, modified 7 Years ago. Liferay Legend Posts: 6441 Join Date: 9/23/08 Recent Posts
Ketan Solanki:
But my primary purpose is to invoke a method of some other Component class from this start method. 
@Component(immediate = true, service = StartupActivator.class)
public class StartupActivator implements BundleActivator {

...
}

I might be wrong here, but a DS as a Bundle Activator sounds weird to me: A BundleActivator is run before the DS initialization is done, thus I'd expect you'll have to decide for one: There are DS-Activators and BundleActivators.

And as they're the source of resource- and memory leaks: Make sure that you deactivate if you're doing anything else than storing a String. Otherwise you might hold on to a service that's long gone and prevent its garbage collection.

 

 

Ketan Solanki, modified 7 Years ago. Junior Member Posts: 63 Join Date: 5/28/14 Recent Posts

Thanks very much Christoph and Olaf for your time and responses. 

@Christoph: I have tried the method you suggested, it gets invoked while portlet is starting up but even in that method the liferayInstanceService remains NULL. And that's true for the start method as well. 

@Olaf: I agree with you that DS and Bundle Activator don't go together! DS doesn't work at all on BundleActivator!

 

So I tried something completely different, like wrote the code of getting the site, and setting the field from the class itself - as below:

private void setRefreshJsonTrue() {
    try {
        String siteName = ApplicationConfig.getInstance().defaultSiteName();

        List<Group> groups = GroupLocalServiceUtil.getGroups(0, 10000);
        for (Group group : groups) {
            if (group.getDescriptiveName().equalsIgnoreCase(siteName)) {
                group.getExpandoBridge().setAttribute("RefreshJson", Boolean.TRUE, true);
            }
        }
    } catch (PortalException pe) {
        LOG.error("Error occurred while getting the site, try again!");
    }
}

I invoked above method from the start() but I got different and, obviously, legitimate exception. 

Caused by: java.lang.RuntimeException: com.liferay.portal.kernel.security.auth.PrincipalException: PermissionChecker not initialized
        at com.liferay.portlet.expando.model.impl.ExpandoBridgeImpl.setAttribute(ExpandoBridgeImpl.java:444)

Because DS runs after bundle activator... 

I am sure someone would have definitely faced an issue like this, where a site custom field is changed everytime a deployment is made! 

Please guide me how to resolve this issue. 

Thanks, 

thumbnail
Tomas Polesovsky, modified 7 Years ago. Liferay Master Posts: 677 Join Date: 2/13/09 Recent Posts

Hi,

 

what you needed was org.osgi.framework.BundleListener, not BundleActivator. BundleListener receives events about other bundles, BundleActivator fires when your module starts. Or better use org.osgi.util.tracker.BundleTracker that is easier and simpler to use.

Related to the PermissionChecker exception: do you need to run the code with some signed in user privileges? It looks rather like a system job or similar, correct? In such case you can call setAttribute(..., false), i.e. something like this:

group.getExpandoBridge().setAttribute("RefreshJson", Boolean.TRUE, false);

 

 Another problem is that OSGi modules usually start before portal is initialized, that means portal Spring services (including Group or Expando services) might not be up. It's a good practice to reference directly GroupLocalService from the component and don't rely on GroupLocalServiceUtil (static class) to make sure portal is up. Otherwise you need to reference this "service" to make sure your component activates after portal is up: @Reference(target = ModuleServiceLifecycle.PORTAL_INITIALIZED, unbind = "-")

So please use this:

@Reference
private GroupLocalService groupLocalService;

private void setRefreshJsonTrue() {
    ...
    List<Group> groups = groupLocalService.getGroups(0, 10000);
    ...
}

 

HTH.

Ketan Solanki, modified 7 Years ago. Junior Member Posts: 63 Join Date: 5/28/14 Recent Posts

Thanks Tomas for your time and response. This statement

group.getExpandoBridge().setAttribute("RefreshJson", Boolean.TRUE, false);

Has done the magic, along with BundleActivator :) so basically when I passed secure: true as the last parameter, Liferay was checking all the permission logic and permission checker wasn't yet initialized so it threw exception!

I tried with BundleTracker but somehow it's overridden methods were not printing the logs while deploying the portal. 

Anyways, problem solved - and once again thanks very much for the solution...