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.
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 ServiceTracker
s. 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.