Overriding OSGi Services
Liferay’s OSGi container is a dynamic environment in which services can be added, removed, or overridden as needed. This framework registers Liferay components with the OSGi service registry, each with their own availability, ranking, and attributes. Together, these details determine how components bind to the services they reference.
To override an OSGi service, you’ll follow these steps:
-
Identify the service you want to override, as well as any components that reference it.
-
Gather the following service details.
-
Service Type: the interface implemented by the service you’re overriding
-
Service’s Class Name: the existing service’s full name
-
-
If applicable, gather the following details for components that reference the service.
-
Component Name: the full name of a component that references the service you’re overriding
-
Reference Name: the field name that references to the target service
-
Reference Policy: whether the reference is
staticordynamic -
Reference Policy-Option: whether the
policy-optionisgreedyorreluctant -
Cardinality: the number of service instances to which the reference can and must bind
Together, a service’s Reference Policy, Reference Policy Option, and Cardinality determine a component’s conditions for adopting new services.
-
-
Create a new service that uses the same interface implemented by the service you’re overriding.
-
Give your service a higher ranking than the service it’s overriding.
-
(Optional) If necessary, reference and invoke the service you’re overriding in your service.
The sample modules demonstrate how to override an OSGi service. These modules include an API for defining a new OSGi service type, an initial implementation of that type, and a generic portlet that references the initial implementation. Also included are alternate implementations of the API to demonstrate how to override the initial implementation.
Deploy Sample Modules for Overriding
Start a new Liferay instance by running
docker run -it -m 8g -p 8080:8080 liferay/portal:7.4.3.132-ga132
Sign in to Liferay at http://localhost:8080. Use the email address test@liferay.com and the password test. When prompted, change the password to learn.
Then, follow these steps:
-
Download and unzip the example modules.
curl https://resources.learn.liferay.com/examples/liferay-s1j6.zip -Ounzip liferay-s1j6.zip -
Run the following
gradlewcommand from thes1j6-api,s1j6-able-impl, ands1j6-websubfolders to build and deploy each module to your new Docker container individually:../gradlew deploy -Ddeploy.docker.container.id=$(docker ps -lq)Each module’s JAR is generated in its
build/libsfolder (e.g.,s1j6-api/build/libs/com.acme.s1j6.api-1.0.0.jar).Log messages indicate when Liferay begins processing and successfully starts each module. These logs also provide each service’s bundle id.
STARTED com.acme.s1j6.api_1.0.0 [1356] STARTED com.acme.s1j6.able.impl_1.0.0 [1357] STARTED com.acme.s1j6.web_1.0.0 [1358] -
Confirm the modules have successfully deployed via the Gogo Shell.
lb | grep -i "s1j6"If successful, the output reads as follows:
1356|Active | 15|Acme S1J6 API (1.0.0)|1.0.0 1357|Active | 15|Acme S1J6 Able Implementation (1.0.0)|1.0.0 1358|Active | 15|Acme S1J6 Web (1.0.0)|1.0.0 true
The provided api defines an OSGi service type that is implemented by the able.impl module, which in turn is used by the provided portlet. Now that everything’s deployed, you can experiment with how overrides work.
Gathering OSGi Service and Reference Details
Once you’ve identified the service you want to override, use the scr:info Gogo Shell command to gather its essential service and reference details. In this example, we want to override the able.impl service.
To gather its service details, run the following command:
scr:info com.acme.s1j6.able.internal.S1J6AbleImpl
Component Description: com.acme.s1j6.able.internal.S1J6AbleImpl
===============================================================
Class: com.acme.s1j6.able.internal.S1J6AbleImpl
Bundle: 1357 (com.acme.s1j6.able.impl:1.0.0)
[...]
Component Configuration Id: 8337
--------------------------------
State: ACTIVE
Service: 17776 [com.acme.s1j6.S1J6]
Used by bundle 1358 (com.acme.s1j6.web:1.0.0)
Config Props: (2 entries)
component.id = 8337
component.name = com.acme.s1j6.able.internal.S1J6AbleImpl
References: (total 0)
This abbreviated output lists the following service details for S1J6AbleImpl:
-
Service Type:
S1J6AbleImplimplements theS1J6interface,com.acme.s1j6.S1J6. -
Service’s Class Name: The service’s full name is
com.acme.s1j6.able.internal.S1J6AbleImpl.
It also indicates that the service is used by a component within the com.acme.s1j6.web:1.0.0 bundle. To view the component’s reference configuration details, run the scr:info command with the component’s full name:
scr:info com.acme.s1j6.web.internal.portlet.S1J6Portlet
Component Description: com.acme.s1j6.web.internal.portlet.S1J6Portlet
=====================================================================
Class: com.acme.s1j6.web.internal.portlet.S1J6Portlet
Bundle: 1358 (com.acme.s1j6.web:1.0.0)
[...]
Component Configuration Id: 8338
--------------------------------
[...]
References: (total 1)
- _s1j6: com.acme.s1j6.S1J6 SATISFIED 1..1 static+greedy
target=(*) scope=bundle (1 binding):
* Bound to [17776] from bundle 1357 (com.acme.s1j6.able.impl:1.0.0)
This abbreviated output lists the following reference configuration details:
Reference Name: The name of the field that references the S1J6AbleImpl service is _s1j6.
Reference Policy: The component’s policy is static (default).
Reference Policy-Option: The component’s policy option is greedy.
Cardinality: Its Cardinality is both mandatory and unary (i.e., 1..1).
While some reference configurations automatically bind to a new or higher ranking service, some require a server restart. Since S1J6Portlet’s reference configuration is static, greedy, mandatory, and unary, no server restart is required before it binds to a new, higher ranking service. See OSGi documentation for more information about how different reference configurations affect a component’s behavior when new or higher ranking services become available.
Creating an OSGi Service with the Gathered Details
Once you’ve gathered the requisite service and reference details, you can use them to create a custom service for overriding and invoking the target service.
-
Declare the service a component within the OSGi framework using the
@Componentannotation. -
Implement the same interface as the target OSGi service and identify its
servicetype within the@Componentannotation. -
Override the interface’s methods.
-
Include the
service.ranking:Integerproperty within the@Componentannotation. Ensure its ranking is higher than the existing service. -
(Optional) Reference the existing service’s
component.nameto invoke it.
The sample S1J6BakerImpl module is provided to override S1J6AbleImpl.
@Component(property = "service.ranking:Integer=100", service = S1J6.class)
public class S1J6BakerImpl implements S1J6 {
@Override
public String doSomething() {
return _s1j6.doSomething() +
"<br />This is the S1J6 Baker implementation.";
}
@Reference(
target = "(component.name=com.acme.s1j6.able.internal.S1J6AbleImpl)"
)
private S1J6 _s1j6;
}
Here, S1J6BakerImpl implements the same service type as S1J6AbleImpl (i.e., com.acme.s1j6.S1J6) and includes the necessary @Component annotation, service attribute, and service.ranking property. It also references the existing service (i.e., component.name=com.acme.s1j6.able.internal.S1J6AbleImpl) and delegates to it as part of overriding the interface’s method. The sample modules also include two other S1J6 implementations for overriding S1J6AbleImpl and then S1J6BakerImpl.
In total, the included implementations have the following rankings:
S1J6AbleImpl: No ranking (defaults to 0)S1J6BakerImpl: 100S1J6CharlieImpl: 101S1J6DogImpl: 101
When deployed, the highest ranking service takes priority and is bound to S1J6Portlet. If more than one service has the same ranking, the first service registered takes priority. Lower ranking services are ignored.
Deploy the Overriding Module and Config File
Follow these steps to deploy S1J6BakerImpl, S1J6CharlieImpl, and S1J6DogImpl:
-
Open the
s1j6-baker-implfolder in your console and run the followinggradlewcommand to build and deploy a JAR file for the module to the Docker container:cd ../s1j6-baker-impl../gradlew deploy -Ddeploy.docker.container.id=$(docker ps -lq)Log messages indicate when Liferay begins processing and successfully starts the module along with its bundle ID.
STARTED com.acme.s1j6.baker_1.0.0 [1359] -
Confirm
S1J6BakerImplhas successfully deployed and bound to your instance via the Gogo Shell.scr:info com.acme.s1j6.web.internal.portlet.S1J6PortletIf successful,
S1J6Portletis bound toS1J6BakerImpl, since it outranksS1J6AbleImpl.References: (total 1) - _s1j6: com.acme.s1j6.S1J6 SATISFIED 1..1 static target=(*) scope=bundle (1 binding): * Bound to [3248] from bundle 1359 (com.acme.s1j6.baker.impl:1.0.0) -
Deploy
S1J6CharlieImplandS1J6DogImplat the same time to the Docker container from thes1j6-liferay.cd ..../gradlew deploy -Ddeploy.docker.container.id=$(docker ps -lq)STARTED com.acme.s1j6.charlie_1.0.0 [1360] STARTED com.acme.s1j6.dog_1.0.0 [1361] -
Confirm both
S1J6CharlieImplandS1J6DogImplhave successfully deployed to your instance via the Gogo Shell.lb -s | grep -i "s1j6"1356|Active | 15|com.acme.s1j6.api (1.0.0)|1.0.0 1357|Active | 15|com.acme.s1j6.able.impl (1.0.0)|1.0.0 1358|Active | 15|com.acme.s1j6.web (1.0.0)|1.0.0 1359|Active | 15|com.acme.s1j6.baker.impl (1.0.0)|1.0.0 1360|Active | 15|com.acme.s1j6.charlie.impl (1.0.0)|1.0.0 1361|Active | 15|com.acme.s1j6.dog.impl (1.0.0)|1.0.0 -
Verify which service is bound to
S1J6Portlet.scr:info com.acme.s1j6.web.internal.portlet.S1J6PortletReferences: (total 1) - _s1j6: com.acme.s1j6.S1J6 SATISFIED 1..1 static target=(*) scope=bundle (1 binding): * Bound to [3249] from bundle 1360 (com.acme.s1j6.charlie.impl:1.0.0)Since both
S1J6CharlieImplandS1J6DogImplhave the same ranking, the service that’s registered first takes priority and is bound toS1J6Portlet.