Zymus
Zymus

Reputation: 1698

How do I create multiple configurations for the same class with Configuration Admin?

I'm working on an entity-component-system game engine that will utilize the OSGi framework. I want users/developers to be able to create their own component types in a modular way, similar to the Bethesda Creation Kit.

Creation Kit Object Window

The way that I had thought about approaching this was to create a class that would represent a component type, then use the Configuration Admin to create configurations, but I'm not sure if my understanding is correct.

I have a class that I want to use as a Component type

@Component(
    configurationPid = "Species",
    configurationPolicy = ConfigurationPolicy.REQUIRE,
    service = Species.class
)
public final class Species {
    // ...
}

To test this, I created a command for Apache Gogo to create a Species. My thought was that I should be able to create multiple species with this command.

@Component(
    property = {
        CommandProcessor.COMMAND_SCOPE + "=species",
        CommandProcessor.COMMAND_FUNCTION + "=create"
    },
    service = CreateSpeciesCommand.class
)
public class CreateSpeciesCommand {

    /* L1 */

    @Reference(bind = "bindConfigurationAdmin")
    private ConfigurationAdmin configurationAdmin;

    @Descriptor("creates a species")
    public void create(@Descriptor("id of the species") final String speciesId) throws IOException, InvalidSyntaxException {
        final String filter = String.format("(%s=%s)", Constants.OBJECTCLASS, Species.class.getSimpleName());
        final Configuration[] existingConfigurations = configurationAdmin.listConfigurations(filter);
        System.out.println(Arrays.toString(existingConfigurations));

        final Configuration speciesConfiguration = configurationAdmin.getConfiguration(Species.class.getSimpleName(), "?");
        Dictionary<String, Object> configProperties = new Hashtable<>();
        configProperties.put(Constants.SERVICE_PID, "Species");

        speciesConfiguration.update(configProperties);
    }
}

But all that happens is that it modifies the configuration, instead of creating a new one.

What do I need to do to create multiple configurations for the same class with the Configuration Admin?

2018-06-19 Edit:

Making the changes specified by Peter Kriens' answer:

results in only one configuration being created, which gets updated with subsequent calls.

Upvotes: 0

Views: 1727

Answers (2)

Zymus
Zymus

Reputation: 1698

In order to get a new configuration for each call, I needed to change the following things:

  • Set the @Component#factory value for the Species class to something unique
  • Use the ConfigurationAdmin#createFactoryConfiguration method instead of the getConfiguration method

I tried applying the changes specified by Peter Kriens' answer:

  • Add @Designate annotation to Species class
  • Set @Component#name to something unique
  • Set @Component#configurationPolicy to ConfigurationPolicy.REQUIRE
  • Add @ObjectClassDefinition to Species.Config class
  • Use ConfigurationAdmin#getConfiguration (createConfiguration does not exist, only this and createFactoryConfiguration) with the @Component#name as the pid; createFactoryConfiguration uses value from @Component#factory

which not only makes new configurations, but also activates the species component at the same time. Not sure why that is, but I'm looking into it.

Updated Code

Species.java

package net.zephyrion.hummingbird.module.species;

import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.ConfigurationPolicy;

import java.util.Objects;

@Component(
    factory = "Species",
    configurationPolicy = ConfigurationPolicy.REQUIRE,
    service = Species.class
)
public final class Species {

    @interface Config {
        String id() default "";
    }
    private Config config;

    @Activate
    public void configure(final Config config) {
        this.config = Objects.requireNonNull(config);
    }

    private String getId() {
        return config.id();
    }
}

CreateSpeciesCommand.java

package net.zephyrion.hummingbird.module.species;

import org.apache.felix.service.command.CommandProcessor;
import org.apache.felix.service.command.Descriptor;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.service.cm.Configuration;
import org.osgi.service.cm.ConfigurationAdmin;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;

import java.io.IOException;
import java.util.Dictionary;
import java.util.Hashtable;
import java.util.List;

@Component(
    property = {
        CommandProcessor.COMMAND_SCOPE + "=species",
        CommandProcessor.COMMAND_FUNCTION + "=create"
    },
    service = CreateSpeciesCommand.class
)
public class CreateSpeciesCommand {

    /* L1 */

    @Reference(bind = "bindConfigurationAdmin")
    private ConfigurationAdmin configurationAdmin;

    @Descriptor("creates a species")
    public void create(@Descriptor("id of the species") final String speciesId) throws IOException {
        try {
            final String factoryPid = Species.class.getSimpleName();
            final String filter = String.format("(&(id=%s)(service.factoryPid=%s))", speciesId, factoryPid);
            final boolean configurationExists = configurationAdmin.listConfigurations(filter) != null;
            if (!configurationExists) {
                final Configuration speciesConfiguration = configurationAdmin.createFactoryConfiguration(factoryPid, "?");
                Dictionary<String, Object> configProperties = new Hashtable<>();
                configProperties.put("id", speciesId);

                speciesConfiguration.update(configProperties);
            }
        }
        catch (InvalidSyntaxException e) {
            e.printStackTrace();
        }
    }

    /* L2 */

    private void bindConfigurationAdmin(final ConfigurationAdmin configurationAdmin) {
        // TODO Obj.rnn
        this.configurationAdmin = configurationAdmin;
    }
}

Upvotes: 0

Peter Kriens
Peter Kriens

Reputation: 15372

OSGi Configuration Admin has 2 different types of configurations:

  • Singleton – Has a PID
  • Factory – Has a PID (for the instance of the factory) and a Factory PID for the 'group' of instances.

In OSGi >= 6, you can therefore do this:

 @Designate( ocd= Species.Config.class, factory=true )
 @Component( name = "species.pid", configurationPolicy=ConfigurationPolicy.REQUIRE )
 public class Species {
      @ObjectClassDefinition
      @interface Config {
          String id();
      }

      @Activate
      void activate( Config config) {
          System.out.println( config.id() );
      }
}

So now the command (extended with list+delete functions):

 @Component(
   property = {
     CommandProcessor.COMMAND_SCOPE + "=species",
     CommandProcessor.COMMAND_FUNCTION + "=create",
     CommandProcessor.COMMAND_FUNCTION + "=list",
     CommandProcessor.COMMAND_FUNCTION + "=delete"
   },
   service = CreateSpeciesCommand.class
 )
 public class CreateSpeciesCommand {
   @Reference
   ConfigurationAdmin configurationAdmin;

   public Configuration create(String speciesId) throws Exception {
     Configuration c = configurationAdmin.createFactoryConfiguration( "species.pid", "?");
     Hashtable<String,Object> d = new Hashtable();
     d.put("id", speciesId);
     c.update( d );
     return c;         
   }

   public Configuration[] list() throws Exception {
     return configurationAdmin.
        listConfigurations( "(service.factoryPid=species.pid)");
   }

   public boolean delete(String id) throws Exception {
     Configuration[] list = configurationAdmin.
        listConfigurations( "(&(service.factoryPid=species.pid)(id="+id+"))");
     if ( list == null) {
        return false;
     }
     for ( Configuration c : list ) {
        c.delete();
     }
     return true;
   }
}

Some notes:

  • Written without compiling, so there might be some compile errors
  • WebConsole is much nicer to create and delete Species
  • Skipped the Gogo annotations for readability
  • clearly no defense into 'filter' injection attacks

Upvotes: 3

Related Questions