OSGi Configuration Properties

Learn the low-level approach of using OSGi configuration key/value pairs to define and expose OSGi configuration data to OSGi services.

Transcript
Let’s take a look at how we can use OSGi configuration properties to provide configuration data to our OSGi service. We’ll be looking at two different ways that we can specify and read in OSGi configuration properties in our OSGi service. The first is a little bit more low-level and you probably won’t actually use that much in real life. However, it’s a good approach to know. So for this we’re going to start by looking at how we can pull out our list of activities and set them via OSGi configurations instead of directly hard coding them into our Java class. So let’s start by going up to our component annotation on our class that turns this class into an OSGi component and adding the property attribute. So this takes a list of strings and the strings are in the format of the OSGi property key, the equal sign, and then the corresponding value.
So since we’ll be defining a list of activities, we can make up a key, and of course this key should probably be semantic. So let’s just call it Activities, and we can set the value to be one of our values for the activity array. So let’s make this a little bit different from what we have currently just so we can see the changes in AEM. So let’s make this Hiking. And before we look at how we can add more values, let’s first take a look at how we can read this single value out based on this key here. We’re going to head back down to our activate method and the activate method has a number of valid method signatures, and one of these signatures takes a map of type string object.
And this map is going to represent a map of all of the OSGi properties and their corresponding values. So we can read out our activities value of Hiking and set it to our activities field here. So we can say this activities is equal to, and what we could do is directly access the value through the map.
However, this returns an object. So we would have to figure out how to coerce this into a string array. But luckily we have some helper utilities that do this job for us. So we can OSGi’s converters util to do this. So let’s go ahead and you do that. We’ll use as the standard converter provided by the library.
We’ll call convert and convert takes the object value, which we can get by calling properties.get with that OSGi property name, and then we can call to and provide it the class we would like the value to be coerced to, which is a string array. So now it’s going to essentially take the values it finds under the OSGi property key activities, and try to turn it into a string array, and then sets it to our activities field.
So in order to get the other activities in our activities list, all we need to do is create more key value pairs using the exact same key name. So we’ll say, and again, I’ll be changing this up just so we can see the values change when we execute it.
And there we go. Now, all three values, Hiking, Jogging, and Walking will be associated with the activities OSGi property, and we’ll read them out as a string array, for use within our OSGi service. So one of the thing we can do is actually specify the type of value that we expect for an OSGi property, and this is done with a special syntax when defining the property name. So let’s make a OSGi configuration property that sets the seed for our random number generator. So let’s just call this random.seed and we can specify the type by putting a colon and then the type name. So in this case we’ll be setting an integer and the available types are all documented on the OSGi component annotations Java docs, so you can check those out.
Otherwise it works exactly the same as before, equal sign and then the value. And again, we can always read this out using the same converters.
Again, we have to make sure that we pass in the actual object that represents the value. So in this case, it’s just random seed. We don’t have to put the colon integer as that is just a descriptor. The key itself has random.seed of course, and then here we can say to (Integer.class) and make sure properties.get, of course. So now we pulling out the value from the random seed OSGi property whose default value up here is set to 10 and we’ll send it to randomSeed, and then we can use that to initialize our random object. And of course we have to make this non final and we can get rid of this since we’re setting it with an activate, anyhow.
So now what we’ve done is we pulled out our list of activities as well as our randomSeed into OSGi configuration properties. So I’m going to go ahead and deploy this and we can check it out just to make sure it’s all working, and our activate method is in fact reading out the correct values. And after we’ve done that we’re going to come back and we’re going to address the elephant in the room as it were, which is: What we’ve done so far is taken a hard-coded list of activities, which we previously hard-coded in our activate method and all we’ve really done is hard-coded them up here in our component annotation. So you may be asking yourself, “Why did we do this and what did it by us?” It’s actually a little bit more code as well as more complexity. So we’re going to come back and take a look at how we can use OSGi configuration files and target these properties with different values based on run mode. But let’s really quickly jump back to AEM and make sure that our activate method is reading out our OSGi properties of Hiking, Jogging, and Walking. So back in AEM we have our test harness that invokes our getRandomActivity so we should start seeing these change when I refresh to our new three activities.
So there we go, Hiking, Jogging, and Walking. So this is absolutely pulling our values from our OSGi properties that we’ve defined up here.
Okay. So in order to parameterize these outside of our Java class, we can define OSGi configuration files. So to do this, we’re going to head over to our UI config project and this project contains all our OSGi configurations. So we drill into this. You’ll see that archetype pre creates a bunch of config folders that are targeted by run mode as well.
So if you’re not familiar with this concept of run mode, AEM as a cloud service wIll start using different run modes depending on the environment, as well as the tier. So all AEM as a Cloud service. Author instances wIll start with the author run mode. Likewise, Ul AEM as a service, published tiers will start with a publish run mode. Likewise we can target just the production environment with prod or a state with stage.
So what we can do it is go into the appropriate folder that we want our configuration to be in. I’ll pick the config folder, which is considered the default global folder here, and what we can do is we can make new OSGi configurations. So these are very simple to make.
All you have to do is know the fully qualified class path to your OSGi service implementation you want to configure. So in this case we can just grab the package name up here, create a new one file. I’ll just paste the package name in, and then we just want to make sure that we put in this ActivitiesImpl, which is the class name for our OSGi service implementation. And then we post fix it with cfg.json so AEM knows that this is in fact in OSGi configuration in the Json format. In here we can define a Json object and this Json object is effectively simply going to model our OSGi properties and values. So we have two keys, activities and randomSeed. So let’s come back here and we can make a key, activities, and since this is Jason, we can actually use an array to model this so we can put in a couple of new activities here.
So again, I’m going to make these different from what we have set as the defaults effectively up here in properties, so we can see the difference and make sure that they’re actually pulling from our configuration.
So we’ll go back to Skateboarding, maybe some Surfing, and Skiing. Okay. And we can also set the random seed if we want to as well. And the syntax for this looks exactly the same as defining the key in the component annotation. So random.seed. We can specify the type by just simply post fixing the colon type to the key name, and we can set a key. And maybe we’ll set this to 32, make sure we put our comma there so it’s well formatted.
And there we go. So now when we deploy our complete application, including our core bundle, as well as our UI config project, we’ll start using these values to drive our OSGi configurations.
So let’s go ahead and try that out.
We’ll jump over to AEM.
And let’s go ahead and refresh this page to see if it’s pulling the activities from our list of activities defined in our OSGi configuration file. So there we go. You start seeing Skiing, Surfing, and Skateboarding should be in there as well. And there we go. So you can see that our OSGi service is now being powered by values that we’ve defined in our OSGi configuration file rather than something that we have hard coded in our OSGi service implementation itself.
So coming back here, you probably have already guessed that when we define the properties and values in the component, they effectively act as default values that are used if no other OSGi configuration values are applicable for those keys. So I want to be clear that this doesn’t necessarily act as the canonical source for OSGi properties. In fact, if we wanted to, we could simply delete all of these and wholly rely on the OSGi properties we’ve defined here.
There is one caveat to that though, because if we go back down into our activate method, you can see that we are trying to coerce the value from our activities as well as randomSeed, and then turn it into the specified types.
If our OSGi configuration file neglected to specify one of these settings or both, when this code is evaluated, it would actually throw a null pointer exception because it would be trying to convert a null object into a string array and a null object into an integer class. It didn’t do that before because we had our properties essentially defaulted above. So a different way you can provide default values is through the same converter tool by setting default value, and you can provide anything you want in here. We could do something like this.
We’ll pick a number, 25.
Okay. So that should cover the basics of OSGi configurations and how we can use them in our OSGi services. Make sure to check out the video that builds on this, that shows you how you can designate an object class definition to model your OSGi configuration object. -

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.Map;
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.util.converter.Converters;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@Component(
    service = { Activities.class },
    property = {
        "activities=Hiking",
        "activities=Jogging",
        "activities=Walking",
        "random.seed:Integer=10"
    }
)
public class ActivitiesImpl implements Activities {
    private static final Logger log = LoggerFactory.getLogger(ActivitiesImpl.class);

    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(Map<String, Object> properties) {

        this.activities = Converters.standardConverter()
                .convert(properties.get("activities"))
                .defaultValue(new String[] {
                    "Default Activity 1",
                    "Default Activity 2"
                })
                .to(String[].class);

        final Integer randomSeed = Converters.standardConverter()
                .convert(properties.get("random.seed"))
                .defaultValue(25)
                .to(Integer.class);

        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

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