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
static
ordynamic
-
Reference Policy-Option: whether the
policy-option
isgreedy
orreluctant
-
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.120-ga120
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/dxp/latest/en/liferay-internals/extending-liferay/liferay-s1j6.zip -O
unzip liferay-s1j6.zip
-
Run the following
gradlew
command from thes1j6-api
,s1j6-able-impl
, ands1j6-web
subfolders 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/libs
folder (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:
S1J6AbleImpl
implements theS1J6
interface,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
@Component
annotation. -
Implement the same interface as the target OSGi service and identify its
service
type within the@Component
annotation. -
Override the interface’s methods.
-
Include the
service.ranking:Integer
property within the@Component
annotation. Ensure its ranking is higher than the existing service. -
(Optional) Reference the existing service’s
component.name
to 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-impl
folder in your console and run the followinggradlew
command 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
S1J6BakerImpl
has successfully deployed and bound to your instance via the Gogo Shell.scr:info com.acme.s1j6.web.internal.portlet.S1J6Portlet
If successful,
S1J6Portlet
is 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
S1J6CharlieImpl
andS1J6DogImpl
at 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
S1J6CharlieImpl
andS1J6DogImpl
have 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.S1J6Portlet
References: (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
S1J6CharlieImpl
andS1J6DogImpl
have the same ranking, the service that’s registered first takes priority and is bound toS1J6Portlet
.