Wednesday, November 4, 2020

OSGI Implementation in AEM

The Open Services Gateway Initiative (OSGi) defines an architecture for developing and deploying modular applications and libraries.

 

OSGi container implementations such as Apache Felix allow you to break your application into multiple modules and thus more easily manage cross-dependencies between them.

 

 The OSGi specification defines two things:

 

·        A set of services that an OSGi container must implement and a contract between the container and your application.

 

·        Developing on the OSGi platform means first building your application using OSGi APIs, then deploying it in an OSGi container.

 

 From a developer's perspective, OSGi offers the following advantages:

 

·        You can install, uninstall, start, and stop different modules of your application dynamically without restarting the container.

 

·        Your application can have more than one version of a particular module running at the same time.

 

·        OSGi provides very good infrastructure for developing service-oriented applications, as well as embedded, mobile, and rich internet apps.

 

 OSGi containers are intended specifically for developing complex Java applications that you want to break up into modules.

 

OSGI in AEM

You can open OSGI Console at localhost:4502/system/console/bundles




OSGI is built on the concept on bundles. Each Bundle has its own class loader. Which allows developers to start and stop each bundle separately.

The OSGI Framework consists of three major parts:- Bundles, Lifecycle, and Services.

 

·         Services layer exposes services objects that can be called from other services running in the OSGi server.

 

·         Life-cycle layer defines sequence of steps that the bundles should go through when installed, started, updated, stopped and uninstalled.

 

·         The Bundle layer is represented as a JAR file.

 

OSGI Bundle Life Cycle



INSTALLED: The OSGi runtime knows the bundle is there.

 

RESOLVED: The bundle is there and all it’s prerequisites (dependencies) are available. The bundle can be started (or has been stopped).

 

STARTING: The bundle is being started. If it has a BundleActivator class, the BundleActivator.start() method is being executed. When done, the bundle will become ACTIVE. Note: Bundles that can be activated lazily (Bundle-ActivationPolicy: lazy) stay in this state until one of their class files is loaded.

 

ACTIVE: The bundle is running.

 

STOPPING: The bundle is being stopped. If it has a BundleActivator class, the BundleActivator.stop() method is being executed. When done, the bundle will become RESOLVED.

 

UNINSTALLED: The bundle has been removed from the OSGi runtime.


Where does OSGI are stored?


On local repository Bundles can be found at:


AEM\crx-quickstart\launchpad\felix

 


In CRX OSGI can be found at 2 locations:

 

All OSGI Configurations are stored at /apps/sling/config or /apps/system/config) by default.

 

The configuration will be saved as a paragraph property jcr:data in binary(non-readable format) on respective node.



How does OSGI dependency get resolved?


Each bundle has two parts component and its associated service. Services communicate with each other to access different components as shown in below diagram.



OSGI Dependency Management

 

The OSGi specification allows to break your application into multiple modules and then manage their dependencies on each other.

 It does this by adding a bundle scope. By default, none of the classes in a bundle are visible from any other bundle; inside bundles they follow the normal rules of the Java language. So, what do you do if you want to access the classes of one bundle from another bundle? The solution is to export packages from the source bundle and then import them into the target bundle.

 

OSGI Services

OSGi architecture is a very good candidate for implementing service-oriented applications. It allows bundles to export services that can be consumed by other bundles without knowing anything about the exporting bundle.

Taken with the ability to hide the actual service implementation class, OSGi provides a perfect combination for service-oriented applications.

In OSGi, a source bundle registers a POJO (you don't have to implement any interfaces or extend from any superclass) with the OSGi container as a service under one or more interfaces.

The target bundle can then ask the OSGi container for services registered under a particular interface. Once the service is found, the target bundle binds with it and can start calling its methods.


Creating Custom OSGI Configuration for Scheduler


SlingSchedulerConfiguration (Interface)

 

/* Here we define attributes for OSGI configuration. */

package com.community.config.core.schedulers;

import org.osgi.service.metatype.annotations.AttributeDefinition;

import org.osgi.service.metatype.annotations.AttributeType;

import org.osgi.service.metatype.annotations.ObjectClassDefinition;

 

@ObjectClassDefinition(name = "SlingSchedulerConfiguration", description = "Sling scheduler configuration")

 

public @interface SlingSchedulerConfiguration {

 

          @AttributeDefinition(name = "Scheduler name", description = "Name of the scheduler", type = AttributeType.STRING)

          public String schedulerName() default "Custom Sling Scheduler Configuration";

 

          @AttributeDefinition(name = "Enabled", description = "True, if scheduler service is enabled", type = AttributeType.BOOLEAN)

          public boolean enabled() default false;

 

          @AttributeDefinition(name = "Cron Expression", description = "Cron expression used by the scheduler", type = AttributeType.STRING)

          public String cronExpression() default "0 * * * * ?";

 

          @AttributeDefinition(name = "Custom Parameter", description = "Custom parameter to be displayed by the scheduler", type = AttributeType.STRING)

          public String customParameter() default "AEM Scheduler Demo";

}

 

CustomScheduler (Class)

 

/* Creating Service by Using @Component annotation */

 

@Component: It is the required annotation to declare java class as component, if this annotation is not declared for a Java class, the class is not declared as a component.


package com.community.config.core.schedulers;

 

import org.apache.sling.commons.scheduler.ScheduleOptions;

import org.apache.sling.commons.scheduler.Scheduler;

import org.osgi.service.component.annotations.Activate;

import org.osgi.service.component.annotations.Component;

import org.osgi.service.component.annotations.ConfigurationPolicy;

import org.osgi.service.component.annotations.Deactivate;

import org.osgi.service.component.annotations.Modified;

import org.osgi.service.component.annotations.Reference;

import org.osgi.service.metatype.annotations.Designate;

import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

 

@Component(service = CustomScheduler.class, immediate = true, configurationPolicy = ConfigurationPolicy.REQUIRE)

@Designate(ocd = SlingSchedulerConfiguration.class)

public class CustomScheduler implements Runnable {

 

          @Reference

          private Scheduler scheduler;

          private String customParameter;

          private int schedulerId;

          private static final Logger log = LoggerFactory.getLogger(CustomScheduler.class);

 

          @Activate

          protected void activate(SlingSchedulerConfiguration config) {

                   schedulerId = config.schedulerName().hashCode();

                   customParameter = config.customParameter();

          }

 

          @Modified

          protected void modified(SlingSchedulerConfiguration config) {

                   removeScheduler();

                   schedulerId = config.schedulerName().hashCode();

                   addScheduler(config);

          }

 

          @Deactivate

          protected void deactivate(SlingSchedulerConfiguration config) {

                   removeScheduler();

          }

 

          private void removeScheduler() {

                   scheduler.unschedule(String.valueOf(schedulerId));

          }

 

          private void addScheduler(SlingSchedulerConfiguration config) {

                   if (config.enabled()) {

                             ScheduleOptions scheduleOptions = scheduler.EXPR(config.cronExpression());

                             scheduleOptions.name(config.schedulerName());

                             scheduleOptions.canRunConcurrently(false);

                             scheduler.schedule(this, scheduleOptions);

                             log.info("Scheduler added");

                   } else {

                             log.info("Scheduler is disabled");

                   }

          }

 

          @Override

`        public void run() {

                   log.info("Custom Scheduler is now running using the passed custom parameter, custom Parameter {}",

                                      customParameter);

          }

 }

 

Open http://localhost:4502/system/console/configMgr and search for SlingSchedulerConfiguration,

Here you can configure by providing custom configuration values as follows