May 2, 2020
Estimated Post Reading Time ~

Using the OSGi Declarative Service in AEM 6.4

Understand a few project changes you might have to make in order to use these new annotations.

With the release of Adobe Experience Manager (AEM) 6.2, greater support for official OSGi Declarative Services was introduced. It’s worthwhile to consider OSGi Declarative Services implementation for your project, not just because AEM as a product is headed in that direction, but also because migration from SCR annotations is easy, and older SCR annotations are supported alongside the new ones. OSGi Declarative Services implementation will provide access to future updates and enhancements as well.

Here are the key project changes that you might have to incorporate in order to use these new annotations:



Service annotation
One of the major differences between SCR annotations and OSGI Declarative Services is in the way service references are defined.

Using SCR annotations, you can easily define service by using the @service annotation.

OSGI Declarative Services require you to define a service reference as an attribute of the component annotation. As such, the equivalent code looks like this:

@Component(service = Servlet.class)

This also brings about a major change in the way Sling Servlets are defined using Declarative Services.

The older way of defining Sling Servlet that used to look something like this:

@SlingServlet(
  resourceTypes="my-aem-project/components/page/page", 
  selectors="training",
  paths="/bin/trainingservlet",
  extensions="html",
  methods="GET",
  label="Training Servlet Code Snippet",
  description="Training Servlet Code Snippet for Medium Blog"
)

will now look more like this:

@Component(
    service = Servlet.class,
    property = {
        "sling.servlet.extensions=html",
        "sling.servlet.selectors=training",
        "sling.servlet.paths=/bin/trainingservlet",
"sling.servlet.paths=/bin/trainingservlet2",
        "sling.servlet.methods=get",
        "sling.servlet.resourceTypes=my-aem-project/components/page/page"
    }
)

Service references property
Another important change is in the way service reference properties are now defined. In SCR Annotations, to define a service reference property, you might have used code that looked like this:

package com.adobe.training.core.schedulers;

import java.util.Dictionary;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Property;
import org.apache.felix.scr.annotations.Reference;
import org.apache.felix.scr.annotations.Service;
import org.apache.sling.commons.osgi.PropertiesUtil;
import org.apache.sling.jcr.api.SlingRepository;
import org.osgi.service.component.ComponentContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@Component(immediate = true, metatype = true, label = "Training Cleanup Service")
@Service(value = Runnable.class)
@Property(name = "scheduler.expression", value = "*/20 * * * * ?") // Every 20
// seconds
public class CleanupScheduledTask implements Runnable {
private final Logger logger = LoggerFactory.getLogger(getClass());
@Reference
private SlingRepository repository;
@Property(label = "Path", description = "Delete this path", value = "/tmp/mypath")
public static final String CLEANUP_PATH = "cleanupPath";
private String cleanupPath;

protected void activate(ComponentContext componentContext) {
configure(componentContext.getProperties());
}

protected void configure(Dictionary<?, ?> properties) {
this.cleanupPath = PropertiesUtil.toString(properties.get(CLEANUP_PATH), null);
logger.info("!!!configure: cleanupPath='{}''", this.cleanupPath);
}

@Override
public void run() {
logger.info("!!!running now");
Session session = null;
try {
session = repository.loginService(null,repository.getDefaultWorkspace());
if (session.itemExists(cleanupPath)) {
session.removeItem(cleanupPath);
logger.info("!!!node deleted");
session.save();
}
} catch (RepositoryException e) {
logger.error("!!!exception during cleanup", e);
} finally {
if (session != null) {
session.logout();
}
}
}
}

Here @Component(immediate = true, metatype = true, label = “Training Cleanup Service”) is used to define a Webconsole Configuration called as “Training Cleanup Service.”

This configuration contain two properties: scheduler.expression and cleanupPath, both of which are defined using the @properties annotation.

Using the OSGI Declarative Service, the equivalent code would look something like this:

package com.adobe.training.core.core.schedulers;

import javax.jcr.RepositoryException;
import javax.jcr.Session;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.metatype.annotations.AttributeDefinition;
import org.osgi.service.metatype.annotations.Designate;
import org.osgi.service.component.annotations.Reference;
import org.osgi.service.metatype.annotations.ObjectClassDefinition;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.sling.jcr.api.SlingRepository;

@Component(service = Runnable.class)
@Designate(ocd = CleanupServiceImpl.Config.class)

public class CleanupServiceImpl implements Runnable {

@ObjectClassDefinition(name = "A simple cleanup task", description = "Simple demo for cron-job like task with properties")
public static @interface Config {

@AttributeDefinition(name = "Cron-job expression")
String scheduler_expression() default "*/30 * * * * ?";

@AttributeDefinition(name = "Concurrent task", description = "Whether or not to schedule this task concurrently")
boolean scheduler_concurrent() default false;

@AttributeDefinition(name = "Cleanup Path", description = "Can be configured in /system/console/configMgr")
String cleanup_path() default "/var/myPath";

@AttributeDefinition(name = "A parameter", description = "Can be configured in /system/console/configMgr")
String myParameter() default "";
}

private static final Logger LOGGER = LoggerFactory.getLogger(CleanupServiceImpl.class);
@Reference
private SlingRepository repository;

private String cleanupPath;

@Activate
protected void activate(final Config config) {
this.cleanupPath = (String.valueOf(config.cleanup_path()) != null) ? String.valueOf(config.cleanup_path())
: null;
LOGGER.info("configure: cleanupPath='{}''", this.cleanupPath);
}

@Override
public void run() {

Session session = null;
try {

session = repository.loginService(null, repository.getDefaultWorkspace());
//LOGGER.info("Obtained session");
if (session.itemExists(cleanupPath) == true) {
session.removeItem(cleanupPath);

session.save();
}
} catch (RepositoryException e) {
LOGGER.info("RepositoryException", e);

} finally {
if (session != null) {
session.logout();
}
}
}
}

Here the properties are defined with the help of an inner interface called as Config, which contains all the properties that need to defined within the web console configuration.

Properties are defined with the help of different JAVA variables, and the underscore(_) in the variable names are automatically converted into dots(.) (e.g., scheduler_expression would be mapped to scheduler.expression).

Similarly, variable names are preceded by @AttributeDefinition, which allows you to define the label and description for your properties.

@ObjectClassDefinition specifies that the upcoming inner interface contains the OSGI property definition, and @Designate is used to map the inner interface to the object class definition.


References
Codebase: https://github.com/varunmitra/aem-enablement/tree/master/AEM6.4%20Declarative%20Services/trainingproject

Further reading and honorable mention: http://www.nateyolles.com/blog/2017/05/osgi-declarative-services-annotations-in-aem


By aem4beginner

No comments:

Post a Comment

If you have any doubts or questions, please let us know.