OSGi Configurations as ObjectClassDefinition Objects

Learn how to create and designate ObjectClassDefinitions as OSGi configuration representations in an OSGi service.

Transcript
So in this video, we’re going to take a look at how we can designate an object class definition to act as our OSGi configuration object. So in the previous video, we explored some of the fundamentals of the OSGi configurations, such as how we can set them at the property component, how we can read them in, set them as field values and use them within our OSGi service, as well as how it was OSGi configurations play into and can feed data into our OSGi configuration properties. So in this video, we’re going to take a more modern approach at how we can define essentially the OSGi configuration contract for this OSGi service using object class definitions. So the first thing we’re going to do is create a new annotation type that is going to act as our OSGi configuration contract. So to do this, we simply use the Java @Interface, and don’t confuse this with an actual Java interface. And then we can give it a name. Normally this is just named config or sometimes for short CFG. So what we want to do next is declare getter functions for each of the OSGi properties that we want our config to model. So we want to create a getter for activities that returns a string array. Since this can return an array of string values for activities, and we’ll want to create one for random seed that returns as Int. So let’s go and do that. So we can just add string array, activities, and we can use the getter syntax for this method. And similarly, we can do the same for random seed. Now notice that I use an underscore to separate random and seed, even though on our proper name we have dot. And this is because in order for our getter to map to this property name, we need to replace any dots with underscores. So this is kind of an implicit conversion here. So if you have any dots in your property names, simply replace them with underscores in your getter method names. The next thing we can do is instead of setting the default values up here in the property attribute, we can move all of these down as well into our config definition. So for activities we can set the default return value to simply be a string array of these three default activity values if we want.
Similarly with the random seed, we can set the default just to be 10 there.
Next we have to annotate this config with the object class definition, annotation.
And once we’ve done that, then we can go up to our OSGi services implementation here and add a second annotation, the first being the component annotation, and we’ll add a designate annotation. And in here we use the OCD attribute, which stands for that same object class definition, and we simply point to our configs class. So we can say ActivitiesImpl.config.class and now config is registered with our OSGi service as this OSGi configuration definition. There’s a couple other annotations that we can use within config, but this is the bare minimum in order to use the config object to define our OSGi configuration. So now that we have our config, let’s go ahead and use it. For this, we’ll head down to our activate method. And if you remember the activate method supports multiple signatures. Previously, we were using the map signature, which takes a map of the OSGi properties and values. But what we can do is switch this out and provide a config, which we just registered using the designate annotation. Now in activate, we can use our getters to read our OSGi property values. So for example, we can set activities, simply be config.activities, and we can get rid of this big block of code. Using the same approach where you can read in our random seed from OSGi configuration, by calling the random_seed getter. And again, we can simply delete this big block of code that manually reads it off of the OSGi property map.
And lastly, since we’ve created our config object here, as well as providing our defaults, there is no real need for us to provide these property key value pairs in the component. So let’s go ahead and delete those.
Going to remove some of these unused imports.
And now we effectively have parity with the previous code. We can define getters for OSGi properties, we can provide defaults. The defaults of course can be overwritten based on any OSGi configurations that are passed in via our OSGi configuration files.
We can read all the values in, in our activate method from our config object, and then we can use those values to drive our code.
So let’s go ahead and quickly deploy to AEM to make sure that this in fact works.
Let’s head back to AEM and just make sure that our random activity is still pulling from our list of activities provided in our OSGi configuration file. So skateboarding, surfing, skiing, and let’s just go ahead and refresh a few times to pull our random values out. Yep, so there we go, looks like it still works just fine. The last thing I want to touch on is how we can use this approach we just explored to automatically generate an OSGi configuration management UI on the AMSDK. So let’s jump over to the AMSDK OSGi console. And remember that this is only available on the AMSDK and not on cloud service. If we head over to OSGi configuration, we get a list of all of the OSGi components and services that have the OSGi configuration manager UI generated for them.
And just to show you an example of what this looks like, let me open up the cross origin resource sharing configuration for WKND, and as you can see, we get a nice web UI with all of the OSGi configuration properties, as well as fields. We have human-friendly labels, descriptions, as well as the OSGi property name.
This UI allows developers to locally make changes to the OSGi configurations to test things out. For example, if I want to add a new allowed origin to test out, I could set this up, I can remove things and change things.
So depending on how your development team works and what they’re comfortable with, this can be a handy tool. So let’s go ahead and see if we can find this for our activities, OSGi service. So go ahead and search for WKND, should show up here under WKND if we have it, and here we go.
The name, of course, isn’t too nice, but let’s go ahead and click into it.
And as you can see, it’s generated a pretty rudimentary version of this UI. We have our derived fuel labels and activities and random seed, and then their corresponding values. We have our derived labels and activities and random seed, and then the input’s displaying their current values. So heading back to our code, we can make this a little bit nicer. So, the first thing we’ll do. In our object class definition, we can provide a name or description for OSGi configuration, and we can even provide a little bit of detail on a per OSGi property basis. So we just have to annotate each getter method with the attribute definition annotation, and just like the object class definition, we can provide names and descriptions as well.
Depending on the field, there are other attributes that you can set. So things like required or minimum, maximum, cardinality, et cetera. These are all documented on the OSGi attribute definition Java docs, but we’re going to keep our simple and simply set the name and description.
We’ll do one more deployment to our local AMSDK and see how it manifests in the SDK’s OSGi configuration manager.
Heading back to the AMSDK, we’ll refresh our OSGi configuration manager, and we can see that now we in fact have the WKND examples title that we set our description for OSGi configuration. We have our labels, we have our OSGi property descriptions as well as the OSGi property names. So as a developer on local SDK, I can come in here and use this UI to make local changes to my OSGi configuration.
You can save these changes back to our local AMSDK, back to our test harness. And now we should start seeing skateboarding and mountain biking.
One last little trick that I want to show you is if we go back and we find our OSGi configuration down at the bottom, you’ll see that the persistent identity or PID is listed. So this may look familiar.
So this is also the final name of our OSGi configuration file, we just simply postfix it with CFG.Jason, but what you can do in the AMSDK is if you make changes to your OSGi configuration and you want to persist with these back, you can simply copy the PID, back to the top, go to OSGi, select OSGi installation configuration printer, paste in the PID of the OSGi or component. Make sure that you have the OSGi configuration Jason format selected, and tap print.
And what this will do is print out the Jason for whatever the active OSGi configuration is for this particular PID. So if you wanted to save this OSGi configuration to be the default for all of our AEM deployments for our app, we can simply copy it, head back to our code base and update our configuration.Jason file for this OSGi service. Okay, so I think that wraps it up for how you can designate an object class definition to model your OSGi configuration, and then use it to read OSGi configuration values out in your activate method and drive business logic in your OSGi service. -

Resources

Code

ActivitiesImpl.java

/core/src/main/java/com/adobe/aem/wknd/examples/core/adventures/impl/ActivitiesImpl.java

package com.adobe.aem.wknd.examples.core.adventures.impl;

import java.util.Random;

import com.adobe.aem.wknd.examples.core.adventures.Activities;

import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Deactivate;
import org.osgi.service.metatype.annotations.AttributeDefinition;
import org.osgi.service.metatype.annotations.Designate;
import org.osgi.service.metatype.annotations.ObjectClassDefinition;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@Component(
    service = { Activities.class }
)
@Designate(ocd = ActivitiesImpl.Config.class)
public class ActivitiesImpl implements Activities {
    private static final Logger log = LoggerFactory.getLogger(ActivitiesImpl.class);

    @ObjectClassDefinition(
        name = "WKND Examples - Activities Service",
        description = "OSGi Service providing information about WKND Adventures activities"
    )
    @interface Config {

        @AttributeDefinition(
            name = "WKND Activities",
            description = "Human-friendly list of activity names"
        )
        String[] activities() default { "Hiking", "Jogging", "Walking" };

        @AttributeDefinition(
            name = "Random Activity Seed",
            description = "Seed used to randomize activity selection"
        )
        int random_seed() default 10;

    }

    private String[] activities;

    private Random random;

    /**
     * @return the name of a random WKND adventure activity
     */
    public String getRandomActivity() {
        int randomIndex = random.nextInt(activities.length);
        return activities[randomIndex];
    }

    @Activate
    protected void activate(Config config) {

        this.activities = config.activities();

        final int randomSeed = config.random_seed();

        this.random = new Random(randomSeed);

        log.info("Activated ActivitiesImpl with activities [ {} ]", String.join(", ", this.activities));
    }

    @Deactivate
    protected void deactivate() {
        log.info("ActivitiesImpl has been deactivated!");
    }
}

com.adobe.aem.wknd.examples.core.adventures.impl.ActivitiesImpl.cfg.json

/ui.config/src/main/content/jcr_root/apps/wknd-examples/osgiconfig/config/com.adobe.aem.wknd.examples.core.adventures.impl.ActivitiesImpl.cfg.json

{
    "random.seed:Integer":12,
    "activities":[
      "Skateboarding",
      "Mountain Biking"
    ]
}
recommendation-more-help
4859a77c-7971-4ac9-8f5c-4260823c6f69