Reputation: 1698
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.
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:
@Designate
annotation to Species
class@Component#name
to something unique@Component#configurationPolicy
to ConfigurationPolicy.REQUIRE
@ObjectClassDefinition
to Species.Config
classConfigurationAdmin#getConfiguration
(createConfiguration
does not exist, only this and createFactoryConfiguration
) with the @Component#name
as the pidresults in only one configuration being created, which gets updated with subsequent calls.
Upvotes: 0
Views: 1727
Reputation: 1698
In order to get a new configuration for each call, I needed to change the following things:
@Component#factory
value for the Species
class to something uniqueConfigurationAdmin#createFactoryConfiguration
method instead of the getConfiguration
methodI tried applying the changes specified by Peter Kriens' answer:
@Designate
annotation to Species
class@Component#name
to something unique@Component#configurationPolicy
to ConfigurationPolicy.REQUIRE
@ObjectClassDefinition
to Species.Config
classConfigurationAdmin#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.
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();
}
}
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
Reputation: 15372
OSGi Configuration Admin has 2 different types of configurations:
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:
Upvotes: 3