Search code examples
liferayosgiliferay-7declarative-services

How can I access a Liferay local service in a non-OSGi portlet?


In an OSGi @Component Portlet, I can access a Liferay local service using OSGi declarative services:

@Reference
private MyExampleLocalService myExampleService;

Beyond injecting the service into my portlet, @Reference also ensures that Liferay will not start my portlet until MyExampleLocalService is available.

In Liferay, I can also deploy portlets in a WAR without @Component or access to declarative services. I know I can access services there using the static utilities like MyExampleLocalServiceUtil.getService(), but this puts my application at risk for a NullPointerException since my portlet may be started before MyExampleLocalService is available.

I’ve tried using @Inject and/or @Reference to inject the service, but that doesn’t work since this is not a @Component portlet. @Inject throws an exception and @Reference doesn’t have any effect.


Solution

  • Liferay’s local services are all OSGi services, so if at all possible, you should obtain your service dependencies via OSGi @Reference or OSGi + CDI @Reference @Inject (works for Portlet 3.0 portlets). With those annotations, OSGi will manage your portlet, starting the portlet and injecting the services once they become available and shutting down the portlet as services become unavailable.1 However, if you aren’t creating a CDI or OSGi based portlet, you cannot use these preferred methods. For exampl, legacy Portlet 2.0 portlets deployed in a WAR and Spring Portlets cannot utilize declarative services.

    Instead these portlets should order to access Liferay local services in a non-OSGi portlet, you should use OSGi ServiceTrackers. ServiceTracker has waitForService and getService methods for obtaining services. If you want to wait for the service (with a timeout), you can call waitForService any time after portlet initialization has completed. Otherwise you can call getService() any time to obtain the service or null if the service isn’t available.


    Do not call ServiceTracker#waitForService in your portlet’s init method! This can cause an intermittent deadlock since Liferay only deploys one bundle/app at a time. If your portlet is deploying and waiting for a service in init, it can prevent the dependency service bundle from completing deployment. But the portlet will never finish deploying because it needs the dependency. Eventually the waiting portlet will time out, but I think there may also be bugs around this because the deadlock appears to persist even after the portlet times out of waiting.


    Here’s some example code to obtain and use a service:

    public final class NonOSGiPortletUsingServices implements Portlet {
      private ServiceTracker<MyExampleLocalService, MyExampleLocalService> myExampleServiceTracker;
    
      @Override
      public void init(final PortletConfig config) throws PortletException {
        // Obtain the current bundle’s OSGi BundleContext from the PortletContext
        // using the magic constant "osgi-bundlecontext"
        final String bundleContextKey = "osgi-bundlecontext";
        final Object bundleContext = config.getPortletContext().getAttribute(bundleContextKey);
        if (bundleContext == null || !(bundleContext instanceof BundleContext)) {
          throw new PortletException(
              "Could not initialize myExampleService. "
                  + bundleContextKey
                  + " not found in PortletContext.");
        }
    
        myExampleServiceTracker =
            new ServiceTracker<>((BundleContext) bundleContext, MyExampleLocalService.class, null);
        myExampleServiceTracker.open();
        final MyExampleService myExampleService =
          myExampleServiceTracker.getService();
        if (myExampleService == null) {
            LOGGER.warn("Required service {} not available.", MyExampleService.class.getName());
        }
        super.init(config);
      }
    
      @Override
      public void destroy() {
        super.destroy();
        myExampleServiceTracker.close();
      }
    
      @Override
      public void render(final RenderRequest renderRequest, final RenderResponse renderResponse)
          throws IOException, PortletException {
        final MyExampleService myExampleService =
            myExampleServiceTracker.getService();
        if (myExampleService == null) {
            renderResponse.getWriter()
              .append("Required service ")
              .append(MyExampleService.class.getName())
              .append(" not available. ")
              .append(this.getClass().getName())
              .append(" unavailable.");
            return;
        }
        // Use myExampleService here...
      }
    }
    

    This method for obtaining services should work for any non-OSGi Liferay portlets such as Portlet 2.0 portlets, Spring portlets, and JSF portlets (with JSF you might prefer to obtain the BundleContext from the ExternalContext). This method works for any OSGi services, not just Liferay’s local services.


    1. OSGi services are ephemeral and may go away at any time.