May 3, 2020
Estimated Post Reading Time ~

Official OSGi Declarative Services Annotations in AEM

By now you're pretty comfortable writing OSGi components and services using the Felix SCR annotations. However, with AEM 6.2 and greater comes support for the official OSGi Declarative Services annotations. This is exciting for two reasons. First, it's the official implementation; and second, it provides access to future improvements and specification advancements. The Felix Maven SCR plugin probably won't be providing much in the way of future enhancements. In fact, taken directly from the Apache Felix Maven SCR Plugin website:

"While the Apache Felix Maven SCR Plugin is a great tool for developing OSGi components using Declarative Services you should use the official annotations from the OSGi R6 specification. The development of the Apache Felix SCR Plugin is in maintenance mode."

The Apache Sling project and the Adobe Experience Manager product are both moving in this direction and it's suggested that you consider it for your project as well. The migration is fairly easy and both annotation styles will work side-by-side while you complete the switch-over.

If you want to skip directly to the examples, I've created a sample project with a service, servlet, filter, event handler and scheduler using the new annotation hosted on GitHub at: https://github.com/nateyolles/aem-osgi-annotation-demo.

Declarative Services
Remember that declarative services is a compile time process. In order for the DS annotations to be effective, they must be handled during the build process. The Apache Felix SCR annotations require the maven-scr-plugin while the OSGi DS annotations require the maven-bundle-plugin version 3.2.0 or greater.

With the maven-scr-plugin you may be used to finding the DS output under /target/classes/OSGI-INF and /target/classes/OSGI-INF/metatype. With the maven-bundle-plugin, you will have to unzip the compiled artifact (the jar file) to find the DS output in its /OSG-INF directory.

Java Packages
Rather than using org.apache.felix.scr.annotations.*, you'll use org.osgi.service.component.annotations.* and org.osgi.service.metatype.annotations.*.

Dependencies
Rather than using the maven-scr-plugin, you need the maven-bundle-plugin version 3.2.0 or greater.

You also need the artifacts org.osgi.service.metatype.annotations and org.osgi.service.component.annotations (currently version 1.3.0) rather than org.osgi.core and org.osgi.compendium. See the provided sample project's POM file for more specifics.

Once you update your project's dependencies, you'll find that your IDE will inform you that the org.apache.felix.scr.annotations.* annotations are deprecated.

Service Configuration
The most noticeable difference between Felix SCR annotations and OSGi DS annotations is where service reference properties are defined. With Felix annotations everything is in the Properties and Property annotations either at the head of the class file or inline. OSGi DS annotations move the service reference properties to it's own class.

The annotations will move to their own class which declutters the component or service. For components with a large amount of options, you might find that you like an independent class, while a component with only one or two properties may be fine as a subclass.

You'll also immediately notice the Activate method becomes much cleaner as the need to use org.apache.sling.commons.osgi.PropertiesUtil to provide default values has been replaced.
OsgiServlet.java
@Component(
immediate = true,
service = Servlet.class,
property = {
"sling.servlet.resourceTypes=project/components/component"
}
)
@Designate(ocd = SampleOsgiServlet.Configuration.class)
public class SampleOsgiServlet extends SlingSafeMethodsServlet {

@Activate
protected void Activate(Configuration config) {
boolean enabled = config.servletname_enabled();
}

@ObjectClassDefinition(name="OSGi Annotation Demo Servlet")
public @interface Configuration {
@AttributeDefinition(
name = "Enable",
description = "Enable the servlet"
)
boolean servletname_enabled() default false;
}
}


Service Reference Properties
The AttributeDefinition method naming convention will immediately seem out of place. It's very Pythonic looking with its use of underscores. You don't need to write your method names that way, but the reason for doing so is that the underscores are converted to dots for display in the Felix console and your OSGi configs. For example, you're probably familiar with seeing properties defined as something like resource.resolver.searchpath. To achieve this in your configuration class, your method would be named resource_resolver_searchpath.

Provided is an example of how to create each individual property type with the newer OSGi annotations:

Configuration.java
package com.nateyolles.aem.osgiannotationdemo.core.services.impl;

import org.apache.commons.lang3.StringUtils;
import org.osgi.service.metatype.annotations.AttributeDefinition;
import org.osgi.service.metatype.annotations.AttributeType;
import org.osgi.service.metatype.annotations.ObjectClassDefinition;
import org.osgi.service.metatype.annotations.Option;

@ObjectClassDefinition(name = "Annotation Demo Service - OSGi")
public @interface Configuration {

@AttributeDefinition(
name = "Boolean Property",
description = "Sample boolean value",
type = AttributeType.BOOLEAN
)
boolean servicename_propertyname_boolean() default true;

@AttributeDefinition(
name = "String Property",
description = "Sample String property",
type = AttributeType.STRING
)
String servicename_propertyname_string() default "foo";

@AttributeDefinition(
name = "Dropdown property",
description = "Sample dropdown property",
options = {
@Option(label = "DAYS", value = "DAYS"),
@Option(label = "HOURS", value = "HOURS"),
@Option(label = "MILLISECONDS", value = "MILLISECONDS"),
@Option(label = "MINUTES", value = "MINUTES"),
@Option(label = "SECONDS", value = "SECONDS")
}
)
String servicename_propertyname_dropdown() default StringUtils.EMPTY;

@AttributeDefinition(
name = "String Array Property",
description = "Sample String array property",
type = AttributeType.STRING
)
String[] servicename_propertyname_string_array() default {"foo", "bar"};

/*
* To create password field, either set the AttributeType or have the
* property name end with "*.password" (or both).
*/
@AttributeDefinition(
name = "Password Property",
description = "Sample password property",
type = AttributeType.PASSWORD
)
String servicename_propertyname_password() default StringUtils.EMPTY;

@AttributeDefinition(
name = "Long Property",
description = "Sample long property",
type = AttributeType.LONG
)
long servicename_propertyname_long() default 0L;
}


SlingServlet Annotation
The SlingServlet annotation is a special case - it's a convenience annotation and unfortunately it's not available anymore. However, the idea is that it will be available in the future as a custom annotation provided by Sling working in the OSGi DS framework when R7 is released. Your current SCR SlingServlet looks something like this (although without all the available properties set):
SlingServletAnnotation.java
@SlingServlet(
metatype = true,
paths = {
"/bin/foo",
"/bin/bar"
},
extensions = {"html"},
selectors = {"foo"},
resourceTypes = {"nt:file", "project/components/component"}
methods = "GET",
label = "Annotation Demo Servlet",
description = "Sample servlet using Felix SCR annotations"
)


Instead, use a regular component annotation with service type Servlet.class. The service reference properties for paths, extensions, selectors, methods and resourceTypes are simple String properties. When setting array properties, set each entry on a new line. See the Apache Sling docs on Servlet Registration for the available service reference properties. When setting a non-String property value such as an Integer or Boolean, include the type such as service.ranking:Integer=100. Here's the OSGi version of that same servlet (again, you probably won't use all the available properties in your servlet):
OsgiServletAnnotation.java
@Component(
service = Servlet.class,
property = {
"sling.servlet.extensions=html",
"sling.servlet.selectors=foo",
"sling.servlet.paths=/bin/foo",
"sling.servlet.paths=/bin/bar",
"sling.servlet.methods=get",
"sling.servlet.resourceTypes=nt:file",
"sling.servlet.resourceTypes=project/components/component"
}
)


Expanded Examples
View more examples in the demonstration project on GitHub: https://github.com/nateyolles/aem-osgi-annotation-demo.

Further Reading
Carsten Ziegeler's blog posts:
Migrating from the Apache Felix SCR Annotations to the OSGi Declarative Services Annotations
OSGi Components – Simply Simple – Part I

OSGi Components – Simply Simple – Part II
OSGi Components – Simply Simple – Part III
Feike Visser's blog post ( @heervisscher):
Using OSGi annotations



By aem4beginner

No comments:

Post a Comment

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