Expand my Community achievements bar.

New example of osgi config files not working with Aem cloud SDK.

Avatar

Level 9

I tried and failed to get osgi config files to work in my own code as discussed here.

I found one example on the web of someone else who has created an OSGI config the same as mine, but unfortunately, they also don't include a config file.

I took their source code, added it to my project, added a config file, and get same result - AEM is ignoring the config file.

I suspect this is a problem with either

  • my env (M1 Mac running Java 11),
  • a bug in the latest AEM cloud SDK,
  • a problem with the wknd sample project.

Here is the example I took:

https://github.com/nateyolles/aem-osgi-annotation-demo

As this project wont build (AnnotationProcessor is not an ImageIO SPI class), I simply copied the following files to the same place in the wknd projectv (see below for exact source code)

  1. SampleOsgiService.java
  2. /impl/Configuration.java
  3. /impl/SampleOsgiServiceImpl.java
  4. SampleOsgiServlet.java

 

package com.adobe.aem.guides.wknd.core.services;

public interface SampleOsgiService {
    String getSettings();
}
package com.adobe.aem.guides.wknd.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  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 = {
            (label = "DAYS", value = "DAYS"),
            (label = "HOURS", value = "HOURS"),
            (label = "MILLISECONDS", value = "MILLISECONDS"),
            (label = "MINUTES", value = "MINUTES"),
            (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;
}

 

 

 

 

package com.adobe.aem.guides.wknd.core.services.impl;

import org.apache.commons.lang3.ArrayUtils;
import org.apache.sling.api.resource.ResourceResolverFactory;
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.component.annotations.Modified;
import org.osgi.service.component.annotations.Reference;
import org.osgi.service.metatype.annotations.Designate;
import com.adobe.aem.guides.wknd.core.services.SampleOsgiService;

@Component(
    immediate = true,
    service = SampleOsgiService.class,
    configurationPid = "com.adobe.aem.guides.wknd.core.services.impl.SampleOsgiServiceImpl"
)
@Designate(
    ocd = Configuration.class
)
public class SampleOsgiServiceImpl implements SampleOsgiService {

    
    private ResourceResolverFactory resolverFactory;

    boolean booleanProp;
    String stringProp;
    String dropdownProp;
    String[] stringArrayProp;
    char[] passwordProp;
    long longProp;

    
    public String getSettings() {
        StringBuilder sb = new StringBuilder();
        sb.append("Sample OSGi Service:\n");
        sb.append("booleanProp: " + booleanProp + "\n");
        sb.append("stringProp: " + stringProp + "\n");
        sb.append("dropdownProp: " + dropdownProp + "\n");
        sb.append("stringArrayProp: " + ArrayUtils.toString(stringArrayProp) + "\n");
        sb.append("passwordProp: " + String.valueOf(passwordProp) + "\n");
        sb.append("longProp: " + longProp + "\n");

        return sb.toString();
    }

    
    
    protected final void activate(Configuration config) {
        booleanProp = config.servicename_propertyname_boolean();
        stringProp = config.servicename_propertyname_string();
        dropdownProp = config.servicename_propertyname_dropdown();
        stringArrayProp = config.servicename_propertyname_string_array();
        passwordProp = config.servicename_propertyname_password().toCharArray();
        longProp = config.servicename_propertyname_long();
    }

    
    protected void deactivate() {
    }
}

 

 

package com.adobe.aem.guides.wknd.core.servlets;

import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.Servlet;
import javax.servlet.ServletException;

import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.SlingHttpServletResponse;
import org.apache.sling.api.servlets.SlingSafeMethodsServlet;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Modified;
import org.osgi.service.component.annotations.Reference;
import org.osgi.service.metatype.annotations.AttributeDefinition;
import org.osgi.service.metatype.annotations.Designate;
import org.osgi.service.metatype.annotations.ObjectClassDefinition;

import com.adobe.aem.guides.wknd.core.services.SampleOsgiService;

@Component(
    immediate = true,
    service = Servlet.class,
    property = {
        "sling.servlet.extensions=txt",
        "sling.servlet.paths=/bin/osgi",
        "sling.servlet.paths=/bin/foo",
        "sling.servlet.methods=get"
    },
    configurationPid = "com.adobe.aem.guides.wknd.core.servlets.SampleOsgiServlet"
)
@Designate(ocd=SampleOsgiServlet.Configuration.class)
public class SampleOsgiServlet extends SlingSafeMethodsServlet {

    private static final long serialVersionUID = 1L;

    
    private SampleOsgiService sampleOsgiService;

    private boolean enabled;

    
    protected void doGet(final SlingHttpServletRequest req,
            final SlingHttpServletResponse resp) throws ServletException, IOException {

        PrintWriter out = resp.getWriter();

        resp.setContentType("text/plain");
        out.write("Annotation Demo Servlet - OSGi - enabled: " + enabled + "\n");
        out.write(sampleOsgiService.getSettings());
    }

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

    @ObjectClassDefinition(name = "Annotation Demo Servlet - OSGi")
    public  Configuration {
        @AttributeDefinition(
            name = "Enable",
            description = "Sample boolean property"
        )
        boolean enabled() default false;
    }
}

 

I added a new conf file in 

 

/aem-guides-wknd/ui.config/src/main/content/jcr_root/apps/wknd/osgiconfig/config/com.adobe.aem.guides.wknd.core.services.impl.SampleOsgiServiceImpl.cfg.json

 

With the following content:

 

{
    "servicename_propertyname_string":"bar",
    "servicename_propertyname_dropdown":"SECONDS"
}

 

Then I deleted the crx dir, restarted the server, and did 

mvn clean install -PautoInstallSinglePackage.

However, when I run http://localhost:4502/bin/osgi I get:

 

Annotation Demo Servlet - OSGi - enabled: false
Sample OSGi Service:
booleanProp: true
stringProp: foo
dropdownProp: 
stringArrayProp: {foo,bar}
passwordProp: 
longProp: 0

 

stringProp should be "bar" and dropdownProp should be "SECONDS"

Just like in my code, the ConfigMngr ends up with two copies of the config:

TB3dock_0-1618007143466.png

 The first one is created from the service, not from the .cfg.json file:

TB3dock_3-1618007254670.png

The second one, which I expected to contain my two values, only has "enable". Looking at the servlet, it also defines a configuration param, in addition to those created on the configuration class. This seems bizarre.

TB3dock_1-1618007197059.png

Below we see the config file is correct in crxde:

TB3dock_0-1618009595813.png

However, this config doesnt seem to exist in the configmgr. I would expect it to be called the same as the pid: com.adobe.aem.guides.wknd.core.services.impl.SampleOsgiServiceImpl.cfg.json but its not there.

6 Replies

Avatar

Community Advisor

Hi @TB3dock 

 

After reading your questions below I just tried to create a sample OSGi service on my local SDK with .cfg.json as the extension for the OSGi configuration file and it works perfectly fine.

So I believe you are missing something while writing code. Please see the sample code below that I have written:

 

Service Interface which has only 3 attributes (2 string and 1 int type), with default value so incase, the configuration values are not provided in the respective runmode, the default values can be picked.

package com.adobe.aem.guides.wknd.core.services;

import org.osgi.service.metatype.annotations.AttributeDefinition;
import org.osgi.service.metatype.annotations.AttributeType;
import org.osgi.service.metatype.annotations.ObjectClassDefinition;

@ObjectClassDefinition(name = "EndPoint Configuration Service", description = "EndPoint Configuration Service")
public @interface EndPointConfigurationService {

@AttributeDefinition(name = "EndPoint URL", description = "Configure EndPoint URL", type = AttributeType.STRING)
String endPointURL() default "https://www.google.co.in";

@AttributeDefinition(name = "EndPoint Name", description = "Configure EndPoint Name", type = AttributeType.STRING)
String endPointName() default "Google India";

@AttributeDefinition(name = "EndPoint Name", description = "Configure EndPoint Name", type = AttributeType.INTEGER)
int timeoutDuration() default 10000;
}

Service Class Implementing the above Interface:

package com.adobe.aem.guides.wknd.core.services.impl;

import com.adobe.aem.guides.wknd.core.services.EndPointConfigurationService;
import org.osgi.framework.Constants;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.metatype.annotations.Designate;

@Component(immediate = true, service = EndPointConfigurationServiceImpl.class,
property = {"process.label=EndPoint Configuration Service",
Constants.SERVICE_DESCRIPTION + "=EndPoint Configuration Service for XXXX Account",
Constants.SERVICE_VENDOR + "=Someone for Testing"})
@Designate(ocd = EndPointConfigurationService.class)
public class EndPointConfigurationServiceImpl {

private EndPointConfigurationService endPointConfigurationService;

@Activate
protected void activate(EndPointConfigurationService config) {
this.endPointConfigurationService = config;
}

public String getEndPointURL() {
return endPointConfigurationService.endPointURL();
}

public String getEndPointName() {
return endPointConfigurationService.endPointName();
}

public int getTimeoutDuration() {
return endPointConfigurationService.timeoutDuration();
}
}

OSGi Configuration that is added on default run mode in AEM:

com.adobe.aem.guides.wknd.core.services.impl.EndPointConfigurationServiceImpl.cfg.json

{
"endPointURL": "https://www.facebook.com",
"endPointName": "Facebook Global",
"timeoutDuration": "50000"
}

Sample Servlet to Invoke the Service:

As of now I have used a path based servlet, but it is preferred to use a Resource based servlet.

 

package com.adobe.aem.guides.wknd.core.servlets;

import com.adobe.aem.guides.wknd.core.services.impl.EndPointConfigurationServiceImpl;
import org.apache.http.entity.ContentType;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.SlingHttpServletResponse;
import org.apache.sling.api.servlets.HttpConstants;
import org.apache.sling.api.servlets.ServletResolverConstants;
import org.apache.sling.api.servlets.SlingSafeMethodsServlet;
import org.osgi.framework.Constants;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.Servlet;
import javax.servlet.ServletException;
import java.io.IOException;

@Component(service = Servlet.class, property = {"process.label=EndPoint Servlet",
Constants.SERVICE_DESCRIPTION + "=" + "This is a test servlet to get the end points from OSGi",
ServletResolverConstants.SLING_SERVLET_METHODS + "=" + HttpConstants.METHOD_GET,
ServletResolverConstants.SLING_SERVLET_PATHS + "=" + "/bin/testendpoint"})
public class EndPointServlet extends SlingSafeMethodsServlet {

private static final long serialVersionUID = 1L;

private static final Logger log = LoggerFactory.getLogger(EndPointServlet.class);

@Reference
private transient EndPointConfigurationServiceImpl endPointConfigurationService;

@Override
protected void doGet(SlingHttpServletRequest request, SlingHttpServletResponse response) throws ServletException, IOException {
log.info("***** :: doGet :: Start :: *****");

response.setContentType(ContentType.TEXT_PLAIN.getMimeType());

final String responseString = "EndPointURL -> " + endPointConfigurationService.getEndPointURL() + "\n" +
"EndPointName -> " + endPointConfigurationService.getEndPointName() + "\n" +
"TimeOut Duration -> " + endPointConfigurationService.getTimeoutDuration();

log.info("***** :: doGet :: End :: *****");

response.getWriter().write(responseString);
}
}

 

Response when the config file is present: 

 

EndPointURL -> https://www.facebook.com
EndPointName -> Facebook Global
TimeOut Duration -> 50000 

 

Response when the config is not present, taking the default value from service:

EndPointURL -> https://www.google.co.in
EndPointName -> Google India
TimeOut Duration -> 10000

 

asutosh_j3_0-1618030566764.png

asutosh_j3_1-1618030600270.png

 

Hope this helps!

Thanks

Avatar

Level 9

Hi, I have spent now 8 man days trying to get a config file read. It simply doesnt work in my env. I suspect its a bug in the latest cloud SDK. Which version of the SDK are you using?  The code I have used here is simply the code from the github example, with nothing but a search and replace on the package names. It works perfectly, with the correct PID etc, except for the .cfg.json files dont work.  i have tried other examples, and get the same problem. I have tried every possible conceivable variation and I have come to the conclusion its not the code, its something in the env, e.g. java 11, the latest cloud SDK, or something specific to M1 mac.  I need to find a way to get round this issue, so thinking of creating a REST api to read config from some external DB.

Avatar

Level 9
Looking at the example you kindly provided, its very different from every other service implementation i have found. In the others, the service interface defines service methods, not the AttributeDefinitions. A service should expose its services, not intrnal cong attributes which are not exposed outside the service?

Avatar

Level 9
Hi, Inspired by your example, I have pasted mine above. It has what I belive is a more logical structure: a separate config class which only defines the config attributes, a service class and impl which only define the services methods. Any idea why this code (which was taken from someones example) doesnt work with the config files?

Avatar

Employee Advisor

First, you don't have "2 copies of the config", they are different ones (check the names and also the PIDs).

 

The OSGI configuration of the Servlet looks correct, because the configuration has just the definition for the enabled boolean property, I don't understand why you consider it as problematic. The default value of the configuration is displayed, because you haven't provided a actual configuration for it.

 

For the service: The "_" in the java names are escaping a "." in the actual property name. Which is a kind of workaround, because at the time when this type of annotations were introduced, a lot of osgi configurations were already out there which were using the "." as separator in the OSGI config names. And the SCR implementation before did not have the annotation and the config classes, there you had to read and convert the properties manually, and for this the API was using plain strings.

 

Please try and use this format as configuration:

{
    "servicename.propertyname.string":"bar",
    "servicename.propertyname.dropdown":"SECONDS"
}

 

I would recommend to use plain Camel Case namings also for the properties, that makes it much more readable, and use "_" only for legacy implementations which are using property names containing dots. So if you change your property names in the configuration to "servicenamePropertyNameDropDown" (or something better), you can use "servicenamePropertyNameDropDown" also in the OSGI configuration.

 

HTH,

Jörg