My main interest was for the AccountManagementService to bind my implementation of the MailService, instead the default implementation, you can achieve this by adding the service.ranking property and giving it an integer higher than the default implementation:
import com.day.cq.mailer.MailService;
import com.day.cq.mailer.MailingException;
import com.day.cq.mailer.MessageGateway;
import org.apache.commons.mail.Email;
import org.apache.commons.mail.EmailException;
import org.osgi.framework.Constants;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import org.osgi.service.component.annotations.ReferenceCardinality;
import org.osgi.service.component.annotations.ReferencePolicy;
@Component(
property = {
"label=Custom Mail Service",
"description=CustomMail Service",
Constants.SERVICE_RANKING + ":Integer=1000"},
service = MailService.class, immediate = true)
public class CustomMailService implements MessageGateway<Email>, MailService {
@Reference(target = "(service.pid=com.day.cq.mailer.DefaultMailService)",
cardinality = ReferenceCardinality.MANDATORY, policy = ReferencePolicy.DYNAMIC)
private volatile MailService defaultMailService;
@Override
public boolean handles(Class<? extends Email> aClass) {
return true;
}
@Override
public void send(Email email) throws MailingException {
// transform email based on some conditions
defaultMailService.send(email);
}
@Override
public void sendEmail(Email email) throws EmailException {
defaultMailService.send(email);
}
}
But I hit a problem
The problem is, the AccountManagementService refuses to bind my CustomMailService unless I refreshed AccountManagementService. The reason is that the AccountManagementService has a static reference to MailService which means that adding a new MailService in OSGI would not cause AccountManagementService to look for “better” service (higher ranking). See the OSGI spec for more details
I realize this is version 7 of the OSGI spec, but the same information is true for OSGI 6, you could download version 6 of the spec here
Now what?
Well, we could use a Bundle Activator and write some code to stop, then start the AccountManagementService. And do that we will!
First, we write a general util class to enable/disable components by providing the service PID, the bundle context and the ServiceComponentRuntime service.
import java.util.Optional;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.service.component.runtime.ServiceComponentRuntime;
import org.osgi.service.component.runtime.dto.ComponentDescriptionDTO;
import org.osgi.util.promise.Deferred;
import org.osgi.util.promise.Promise;
public class OSGIComponentUtil {
public static ComponentDescriptionDTO getDTO(BundleContext bundleContext, ServiceComponentRuntime scr, String componentName){
for (Bundle bundle : bundleContext.getBundles()) {
ComponentDescriptionDTO dto = scr.getComponentDescriptionDTO(bundle, componentName);
if (dto != null) {
return dto;
}
}
return null;
}
public static Promise<Void> enable(ComponentDescriptionDTO dto, ServiceComponentRuntime scr){
return Optional.ofNullable(dto)
.map(scr::enableComponent)
.orElse(getFailedPromise());
}
public static Promise<Void> disable(ComponentDescriptionDTO dto, ServiceComponentRuntime scr) {
return Optional.ofNullable(dto)
.map(scr::disableComponent)
.orElse(getFailedPromise());
}
public static Promise<Void> getFailedPromise(){
Deferred deferred = new Deferred<Void>();
deferred.fail( new Exception());
return deferred.getPromise();
}
}
Now let’s write the bundle activator:
import com.adobe.cq.account.api.AccountManagementService;
import com.company.core.utils.OSGIComponentUtil;
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceReference;
import org.osgi.service.component.runtime.ServiceComponentRuntime;
import org.osgi.service.component.runtime.dto.ComponentDescriptionDTO;
public class CustomBundleActivator implements BundleActivator {
@Override
public void start(BundleContext bundleContext) throws Exception {
refreshAccountManagementService(bundleContext);
}
@Override
public void stop(BundleContext bundleContext) throws Exception {
refreshAccountManagementService(bundleContext);
}
private void refreshAccountManagementService(BundleContext bundleContext){
ServiceComponentRuntime scr = getServiceComponentRuntime(bundleContext);
ComponentDescriptionDTO dto = getAccountManagementServiceDTO(bundleContext, scr);
OSGIComponentUtil.disable(dto, scr)
.then(p -> OSGIComponentUtil.enable(dto, scr));
}
private ServiceComponentRuntime getServiceComponentRuntime(BundleContext bundleContext){
ServiceReference reference = bundleContext.getServiceReference(ServiceComponentRuntime.class.getName());
return (ServiceComponentRuntime) bundleContext.getService(reference);
}
private ComponentDescriptionDTO getAccountManagementServiceDTO(BundleContext bundleContext, ServiceComponentRuntime scr){
return OSGIComponentUtil.getDTO(bundleContext, scr, AccountManagementService.class.getName());
}
}
and then providing the bundle activator class in the configuration for the maven-bundle-plugin:
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
<configuration>
<instructions>
...
<Bundle-Activator>com.company.core.activator.CustomBundleActivator</Bundle-Activator>
</instructions>
</configuration>
</plugin>
and voila! the AccountManagementService is now refreshed every time this bundle is deployed! You could also write some code to check if the custom mail service really got a bond to the AccountManagementService.
This implementation was really helpful with the code above: ComponentDisablerDriverDS13.java
The problem is, the AccountManagementService refuses to bind my CustomMailService unless I refreshed AccountManagementService. The reason is that the AccountManagementService has a static reference to MailService which means that adding a new MailService in OSGI would not cause AccountManagementService to look for “better” service (higher ranking). See the OSGI spec for more details
I realize this is version 7 of the OSGI spec, but the same information is true for OSGI 6, you could download version 6 of the spec here
Now what?
Well, we could use a Bundle Activator and write some code to stop, then start the AccountManagementService. And do that we will!
First, we write a general util class to enable/disable components by providing the service PID, the bundle context and the ServiceComponentRuntime service.
import java.util.Optional;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.service.component.runtime.ServiceComponentRuntime;
import org.osgi.service.component.runtime.dto.ComponentDescriptionDTO;
import org.osgi.util.promise.Deferred;
import org.osgi.util.promise.Promise;
public class OSGIComponentUtil {
public static ComponentDescriptionDTO getDTO(BundleContext bundleContext, ServiceComponentRuntime scr, String componentName){
for (Bundle bundle : bundleContext.getBundles()) {
ComponentDescriptionDTO dto = scr.getComponentDescriptionDTO(bundle, componentName);
if (dto != null) {
return dto;
}
}
return null;
}
public static Promise<Void> enable(ComponentDescriptionDTO dto, ServiceComponentRuntime scr){
return Optional.ofNullable(dto)
.map(scr::enableComponent)
.orElse(getFailedPromise());
}
public static Promise<Void> disable(ComponentDescriptionDTO dto, ServiceComponentRuntime scr) {
return Optional.ofNullable(dto)
.map(scr::disableComponent)
.orElse(getFailedPromise());
}
public static Promise<Void> getFailedPromise(){
Deferred deferred = new Deferred<Void>();
deferred.fail( new Exception());
return deferred.getPromise();
}
}
Now let’s write the bundle activator:
import com.adobe.cq.account.api.AccountManagementService;
import com.company.core.utils.OSGIComponentUtil;
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceReference;
import org.osgi.service.component.runtime.ServiceComponentRuntime;
import org.osgi.service.component.runtime.dto.ComponentDescriptionDTO;
public class CustomBundleActivator implements BundleActivator {
@Override
public void start(BundleContext bundleContext) throws Exception {
refreshAccountManagementService(bundleContext);
}
@Override
public void stop(BundleContext bundleContext) throws Exception {
refreshAccountManagementService(bundleContext);
}
private void refreshAccountManagementService(BundleContext bundleContext){
ServiceComponentRuntime scr = getServiceComponentRuntime(bundleContext);
ComponentDescriptionDTO dto = getAccountManagementServiceDTO(bundleContext, scr);
OSGIComponentUtil.disable(dto, scr)
.then(p -> OSGIComponentUtil.enable(dto, scr));
}
private ServiceComponentRuntime getServiceComponentRuntime(BundleContext bundleContext){
ServiceReference reference = bundleContext.getServiceReference(ServiceComponentRuntime.class.getName());
return (ServiceComponentRuntime) bundleContext.getService(reference);
}
private ComponentDescriptionDTO getAccountManagementServiceDTO(BundleContext bundleContext, ServiceComponentRuntime scr){
return OSGIComponentUtil.getDTO(bundleContext, scr, AccountManagementService.class.getName());
}
}
and then providing the bundle activator class in the configuration for the maven-bundle-plugin:
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
<configuration>
<instructions>
...
<Bundle-Activator>com.company.core.activator.CustomBundleActivator</Bundle-Activator>
</instructions>
</configuration>
</plugin>
and voila! the AccountManagementService is now refreshed every time this bundle is deployed! You could also write some code to check if the custom mail service really got a bond to the AccountManagementService.
This implementation was really helpful with the code above: ComponentDisablerDriverDS13.java
No comments:
Post a Comment
If you have any doubts or questions, please let us know.