January 1, 2021
Estimated Post Reading Time ~

OSGi Factory Configuration implementation

OSGI Factory configuration is used to have many instances of an OSGI config (represented/identified via an identifier) for the single OSGI service/PID.
  • Most often used OOB Factory configurations for example:
  • Logger Factory/writer configuration (for log statements)
  • Service user Mapper service Amendment (for using service resource resolver in projects)
Few sample real-time use case for the need for OSGI factory config:
  • For multi-tenant projects, if we have generic service used across tenants with properties/attributes specific to the tenant.
  • For single-tenant, generic service targeting all locales with properties/attributes specific to the locale. (where generic service -> holds logic in the lines of calling third party service for retrieving/updating information.)
Implementation: (In terms of OSGi R6 annotations):
  • Create Object Class Definition(OCD) defining desired properties for the OSGI service. - OSGIFactoryConfigOCD.java
  • OSGi Service/Factory service to retrieve config values. - OSGIFactoryConfigDemo/OSGIFactoryConfigDemoImpl.java
  • Business logic to be developed as OSGI service/Servlet/Sling Model or any java class where OSGI factory service(created in #2) can be referenced. - In this example, have used servlet, OSGIFactoryConfigDemoServlet.java
OCD:
For the sample real-time use case mentioned above, let's say
  • We need to configure API endpoint and AppId/any similar properties to access the same.
  • Configure another property named Sitename or locale or any identifier based on the actual requirement and existing project set up.
OSGIFactoryConfigOCD.java
@ObjectClassDefinition(name="OCD for Factory Config Demo", description="OCD for Factory config demo")
public @interface OSGIFactoryConfigOCD {

    @AttributeDefinition(name="Endpoint URL", description="Sample Endpoint URL     for connecting to third party", type=AttributeType.STRING)
    String apiEndpoint() default "http://xyz.com";

    @AttributeDefinition(name="API Key", description="Sample API key for        
    connecting to third party", type=AttributeType.STRING)
    String apiKey() default "XYZ";

    @AttributeDefinition(name="SiteName", description="Sample Site name used as     an identifier for respective factory config", type=AttributeType.STRING)
    String siteNameIdentifier() default "demosite";

}


OSGi/OSGi Factory service:
  • OSGi service can be declared as factory service as below (factory=true on @Designate annotation)
  • Defines methods to retrieve the config values which will be available once the service is activated. (config in the below snippet holds the entire OSGI config values)
OSGiFactoryConfigDemoImpl.java
@Component(service=OSGIFactoryConfigDemo.class,immediate=true,configurationPolicy = ConfigurationPolicy.REQUIRE)
@Designate(ocd=OSGIFactoryConfigOCD.class, factory=true)
public class OSGiFactoryConfigDemoImpl implements OSGIFactoryConfigDemo {

@Activate
protected void activate(OSGIFactoryConfigOCD config) {
        this.apiEndPoint = config.apiEndpoint();
        this.apiKey = config.apiKey();
        this.siteName = config.siteNameIdentifier();
    }
}

At this stage when we build and deploy the code, we are ready to create multiple OSGI configs via Felix console or in the config folders by creating a node of type sling:OsgiConfig in the name of Service PID with a unique identifier, say -> Service PID-identifier. In this example, it is learnings.core.serviceimpl.OSGiFactoryConfigDemoImpl-demosite

Java class referencing Factory service:
Targeting a specific instance:
  • Let's say we have created 5 OSGI configs of the same PID with a unique identifier, one each for the tenant and if there is a need to target one such tenant, we can reference as below.
  • Out of 5, config which has sitenameIdentifier property value as the demo site will be picked up.
@Reference(target="(siteNameIdentifier=demosite)")
private OSGIFactoryConfigDemo osgiDemo;


Multiple instance reference:
  • bind method will be called when the factory service is available and configs are stored in the collection. In this case, the map is used where “key” -> Sitename, one of the config property, and “value” -> entire config object.
  • Let say, we have created 5 OSGI configs of this PID and if the service is active -> All 5 configs will be available in the map object.
private Map<String,OSGIFactoryConfigDemo> configMap;
@Reference(name = "osgiFactoryConfigDemo", cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC)
protected synchronized void bindOSGIFactoryConfigDemo(final OSGIFactoryConfigDemo config) {
if (configMap == null) {
configMap = new HashMap<>();
}
configMap.put(config.getSiteName(), config);
}
protected synchronized void unbindOSGIFactoryConfigDemo(final OSGIFactoryConfigDemo config) {
configMap.remove(config.getSiteName());
}

Let say, we are able to extract the sitename(key used in the map) from servlet request. (Changes to be taken care of in the way we call the servlet accordingly) Using that, we can retrieve the respective map value(its entire config) and use it accordingly.

On a similar line, based on the overall requirement + existing project set up + with an understanding of factory config implementation, we can decide on the ways of retrieving multiple configs of the same PID and arriving at generic service.

Code on GitHub Repo:
aemlearnings-samples (Above mentioned java files in a respective package inside core)


By aem4beginner

No comments:

Post a Comment

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