OSGi has become a platform capable of running large applications for a variety of purposes, including rich client applications, server-side systems and cloud and container based architectures. As these applications are generally based on many bundles, describing each bundle individually in an application definition becomes unwieldy once the number of bundles reaches a certain level.
When developing large scale applications it is often the case that few people know the role of every single bundle or configuration item in the application. To keep the architecture understandable a grouping mechanism is needed that allows for the representation of parts of the application into larger entities that keep reasoning about the system manageable. In such a domain members of teams spread across an organization will need to be able to both develop new parts for the application as well as make tweaks or enhancements to parts developed by others such as adding configuration and resources or changing one or more bundles relevant to their part of the application.
The higher level constructs that define the application should be reusable in different contexts, for example if one team has developed a component to handle job processing, different applications should be able to use it, and if needed tune its configuration or other aspects so that it works in each setting without having to know each and every detail that the job processing component is built up from.
Applications are often associated with additional resources or metadata, for example database scripts or custom artifacts. By including these with the application definition, all the related entities are encapsulated in a single artifact.
By combining various applications or subsystems together, systems are composed of existing, reusable building blocks, where all these blocks can work together. Architects of these systems need to think about components without having to dive into the individual implementation details of each subcomponent. The Features defined in this specification can be used to model such applications. Features contain the definition of an application or component and can be composed into larger systems.
-
Declarative - Features are declarative and can be mapped to different implementations.
-
Extensible - Features are extensible with custom content to facilitate all information related to a Feature to be co-located.
-
Human Readable - No special software is needed to read or author Features.
-
Machine Readable - Features can easily be processed by tools.
The following entities are used in this specification:
-
Feature - A Feature contains a number of entities that, when provided to a launcher can be turned into an executable system. Features can also be building blocks which are assembled into larger systems.
-
Bundles - A Feature can contain one ore more bundles.
-
Capabilities and Requirements - A Feature can declare additional capabilities and requirements.
-
Configuration - A Feature can contain configurations for the Configuration Admin service.
-
Extension - A Feature can contain a number of extensions with custom content.
-
Launcher - A launcher can turn one or more Features into an executable system.
-
Processor - A Feature processor can read Features and perform a processing operation on them, such as validation, transformation or generation of new entities based on the Features.
-
Properties - Framework properties can be specified in a Feature.
Features are defined by declaring JSON documents or by using the Feature API. Each Feature has a unique ID which includes a version. It can hold a number of entities, including a list of bundles, configurations, capabilities, requirements and others. Features are extensible, that is a Feature can also hold any number of custom entities which are related to the Feature.
Features may have dependencies on other Features. Features inherit the capabilities and requirements from all bundles listed in the Feature, and can also have additional capabilities and requirements declared on the Feature level.
Once created, a Feature is immutable. Its definition cannot be modified. However another Feature with a different identity can be created which is based on a given Feature using the prototype mechanism.
Additionally it’s possible to record caching related information in a Feature through transient extensions, however this cached content is not significant for the definition of the Feature or part of its identity.
Identifiers used throughout this specification are defined using the Maven Identifier model. They are composed of the following parts:
-
Group ID
-
Artifact ID
-
Version
-
Type (optional)
-
Classifier (optional)
For more information see [3] Apache Maven Pom Reference. The format used to specify identifiers is as follows:
group-id ':' artifact-id [ ':' type [ ':' classifier ] ] ':' version
Each Feature has a unique identifier. Apart from providing a persistent handle to the Feature it also provides enough information to find the Feature in an artifact repository. This identifier is defined using the format described in Identifiers. As Features are immutable, a given Feature identifier always refers to the same Feature. ### Mention the Maven artifact packaging type: osgifeature
A Feature can have the following attributes:
Table 708.1 Feature Attributes
Attribute | Data Type | Kind | Description |
---|---|---|---|
name | String | Optional | The short descriptive name of the Feature. |
description | String | Optional | A longer description of the Feature. |
isComplete | boolean | Optional, defaults to false |
Completeness of the Feature. A Feature is complete when it has no external dependencies. |
isFinal | boolean | Optional, defaults to false |
If the Feature is final. A final Feature cannot be used as a prototype for another Feature. |
license | String | Optional | The license of the Feature. |
vendor | String | Optional | The vendor of the Feature. |
An initial Feature without content can be declared as follows:
{
"id": "org.acme:acmeapp:1.0.0",
"name": "The ACME app",
"description":
"This is the main ACME app, from where all functionality can be reached."
/*
Additional Feature entities here
...
*/
}
Features can also be created, read and written using the Feature API. The Feature API uses the builder pattern to create most entities used in Features.
A builder is used to create a single entity and cannot be re-used to create a second one. Builders are obtained from the BuilderFactory
BuilderFactory factory = // ... from Service Registry ...
FeatureBuilder builder = factory.newFeatureBuilder(
new ID("org.acme", "acmeapp", "1.0.0"));
builder.setName("The ACME app");
builder.setDescription("This is the main ACME app, "
+ "from where all functionality can be reached.");
Feature f = builder.build();
Comments in the form of [2] JSMin (The JavaScript Minifier) comments are supported, that
is, any text on the same line after //
is ignored and any
text between /* */
is ignored.
Features often list a number of bundles that implement the functionality provided by the Feature. Bundles are listed
by
referencing them in the bundles
array so that they can be resolved from a repository. Bundles can have
metadata associated with them, such
as the relative start order of the bundle in the Feature.
Custom metadata can also be provided. A single Feature can
provide multiple versions of the same bundle, if desired.
Bundles are referenced using the identifier format described in
Identifiers.
This means that Bundles are referenced using their Maven coordinates. The bundles
array can either contain
String
values referencing the bundles directly by their identifier, or JSON objects which can contain the bundle
IDs with additional metadata.
The following example shows a simple Feature describing a small application with its dependencies:
{
"id": "org.acme:acmeapp:1.0.1",
"name": "The Acme Application",
"license": "https://opensource.org/licenses/Apache-2.0",
"isComplete": true,
"bundles": [
"org.osgi:org.osgi.util.function:1.1.0",
"org.osgi:org.osgi.util.promise:1.1.1",
{
"id": "org.apache.commons:commons-email:1.5",
// This attribute is used by custom tooling to
// find the associated javadoc
"org.acme.javadoc.link":
"https://commons.apache.org/proper/commons-email/javadocs/api-1.5"
},
"com.acme:acmelib:1.7.2"
]
/*
Additional Feature entities here
...
*/
}
Arbitrary key-value pairs can be associated with bundle entries to store custom metadata alongside the bundle references. Reverse DNS naming should be used with the keys to avoid name clashes when metadata is provided by multiple entities.
Bundle metadata supports String keys and String values.
A Feature with Bundles can be created using the Feature API as follows:
BuilderFactory factory = // ... from Service Registry ...
FeatureBuilder builder = factory.newFeatureBuilder(
new ID("org.acme", "acmeapp", "1.0.1"));
builder.setName("The Acme Application");
builder.setLicense("https://opensource.org/licenses/Apache-2.0");
builder.setComplete(true);
FeatureBundle b1 = factory.newBundleBuilder(
ID.fromMavenID("org.osgi:org.osgi.util.function:1.1.0"))
.build();
FeatureBundle b2 = factory.newBundleBuilder(
ID.fromMavenID("org.osgi:org.osgi.util.promise:1.1.1"))
.build();
FeatureBundle b3 = factory.newBundleBuilder(
ID.fromMavenID("org.apache.commons:commons-email:1.1.5"))
.addMetadata("org.acme.javadoc.link",
"https://commons.apache.org/proper/commons-email/javadocs/api-1.5")
.build();
FeatureBundle b4 = factory.newBundleBuilder(
ID.fromMavenID("com.acme:acmelib:1.7.2"))
.build();
builder.addBundles(b1, b2, b3, b4);
Feature f = builder.build();
Features support configuration using the OSGi Configurator syntax, see
Configurator Specification. This can be specified with the
configurations
key in the Feature. A Launcher can apply
these configurations to the Configuration Admin service when starting
the system.
It is an error to define the same PID (or Factory PID) twice in a single Feature.
Example:
{
"id": "org.acme:acmeapp:osgifeature:configs:1.0.0",
"configurations": {
"org.apache.felix.http": {
"org.osgi.service.http.port": 8080,
"org.osgi.service.http.port.secure": 8443
}
}
}
When a Feature is launched in an OSGi framework it may be necessary to specify Framework properties. These can be provided in the Framework Properties section of the Feature. The Launcher must be able to satisfy the specified properties. If it cannot ensure that these are present in the running Framework the launcher must fail.
### TODO to specify failure behavior in all sections.Example:
{
"id": "org.acme:acmeapp:osgifeature:fw-props:2.0.0",
"framework-properties": {
"org.osgi.framework.system.packages.extra":
"javax.activation;version=\"1.1.1\"",
"org.osgi.framework.bootdelegation": "javax.activation"
}
}
Configurations and Framework Properties support late binding of values. This enables setting these items through a Launcher, for example to specify a database user name, server port number or other information that may be variable between runtimes.
Variables are declared in the variables
section of the Feature and they can have a default value specified.
The default can be of type String
, number
or boolean
. Variables can also be declared
to not have a default, which means that they must be provided with a value through the Launcher.
This is done by specifying null
as the default in the variable declaration.
Example:
{
"id": "org.acme:acmeapp:osgifeature:configs:1.1.0",
"variables": {
"http.port": 8080,
"db.username": "scott",
"db.password": null
},
"configurations": {
"org.acme.server.http": {
"org.osgi.service.http.port:Integer": "${http.port}"
},
"org.acme.db": {
"username": "${db.username}-user",
"password": "${db.password}"
}
}
}
Variables are referenced with the curly brace placeholder syntax: ${
variable-name
}
in the configuration value or framework property value section.
To support conversion of variables to non-string types the configurator syntax
specifying the datatype with the configuration key can be used, as in the above example.
Multiple variables can be referenced for a single configuration or framework property value and variables can be combined
with text.
Variable substitution is applied recursively, that is if the value of a variable is again a variable it must be substituted.
If no variable exist with the given name, then the ${
variable-name
}
must be retained in the value.
Features inherit all capabilities and requirements from their bundles. Additional capabilities and requirements can be added to the Feature directly.
For example, a Feature can declare a set of bundles and configurations which together
may provide an osgi.implementation
capability, meaning that sum of these provide the implementation of a specification.
Any kind of capability can be declared to be provided by the Feature. These capabilities can be used as part of the
resolution
process when a number of Features are resolved together.
Additional requirements can mean that a Feature is only complete, when these requirements are also satisfied, in addition to the requirements provided in the bundles that are part of the Feature.
Example:
{
"id": "org.acme:acmeapp:osgifeature:capreq:1.0.0",
// Requirements over and above the requirements in the bundles
// referenced by the Feature.
"requirements": [
{
"namespace": "osgi.contract",
"directives": {
"filter": "(&(osgi.contract=JavaServlet)(version=3.1))"
}
}
],
// Capabilities over and above the capabilities provided by the
// bundles referenced by the Feature.
"capabilities": [
{
"namespace": "osgi.service",
"attributes": {
"objectClass:List<String>":
"org.osgi.service.http.runtime.HttpServiceRuntime"
}
}
]
}
Features can be extended with custom content. This makes it possible to keep entities and information relating to the Feature together with the rest of the Feature.
Custom content is provided through Feature extensions, and can be in one of the following formats:
-
Text - A text extension contains a block of text.
-
JSON - A JSON extension contains embedded custom JSON content.
-
Artifacts - A list of custom artifacts associated with the Feature.
Extensions can have a variety of consumers. For example they can be handled by a Feature Launcher or by an external tool which can process the extension at any point of the Feature life cycle.
Extensions can be marked as one of the following three kinds:
-
Mandatory - The entity processing this Feature must know how to handle this extension.
-
Optional - This extension is optional. If the entity processing the Feature cannot handle it, the extension can be skipped or ignored. This is the default.
-
Transient - This extension contains transient information which can be used to optimize the processing of the Feature. It is not part of the Feature definition.
Extensions are specified as JSON objects under the extensions
key in the Feature. A Feature can
contain any number of extensions, as long as the extension keys are unique. It is recommended to use reverse
DNS naming for extension keys to avoid name clashing of multiple extensions in a single Feature.
Extensions names without DNS prefix are reserved for OSGi use.
Text extensions support the addition of custom text content to the Feature. The text is provided as a JSON string value or as a JSON array of strings. ### update the API to report text as an array
Example:
{
"id": "org.acme:acmeapp:2.0.0",
"name": "The Acme Application",
"license": "https://opensource.org/licenses/Apache-2.0",
"isComplete": true,
"bundles": [
"org.osgi:org.osgi.util.function:1.1.0",
"org.osgi:org.osgi.util.promise:1.1.1",
"com.acme:acmelib:2.0.0"
],
"extensions": {
"org.acme.mydoc": {
"type": "text",
"text": [
"This application provides the main acme ",
"functionality."
]
}
}
}
Custom JSON content can be added to Features by using a JSON extension. The content can either be a JSON object or a JSON array.
The following example extension declares under which execution environment the Feature is complete, using a custom JSON object.
{
"id": "org.acme:acmeapp:2.1.0",
"name": "The Acme Application",
"license": "https://opensource.org/licenses/Apache-2.0",
"isComplete": true,
"bundles": [
"org.osgi:org.osgi.util.function:1.1.0",
"org.osgi:org.osgi.util.promise:1.1.1",
"com.acme:acmelib:2.0.0"
],
"extensions": {
"execution-environment": {
"type": "json",
"json": {
"environment-capabilities":
["osgi.ee; filter:=\"(&(osgi.ee=JavaSE)(version=11))\""],
"framework": "org.osgi:core:6.0.0",
"provided-features": ["org.acme:platform:1.1"]
}
}
}
}
Custom extensions can be used to associate artifacts that are not listed
as bundles
with the Feature.
For example, database definition resources can be listed as artifacts in a Feature. In
the following example, the extension org.acme.ddlfiles
lists
Database Definition Resources which must be handled by
the launcher agent, that is, the database must be configured when the application is run:
{
"id": "org.acme:acmeapp:2.2.0",
"name": "The Acme Application",
"license": "https://opensource.org/licenses/Apache-2.0",
"isComplete": true,
"bundles": [
"org.osgi:org.osgi.util.function:1.1.0",
"org.osgi:org.osgi.util.promise:1.1.1",
"com.acme:acmelib:2.0.0"
],
"extensions": {
"org.acme.ddlfiles": {
"kind": "mandatory",
"type": "artifacts",
"artifacts": ["org.acme:appddl:1.2.1"]
}
}
}
### Do we want to support metadata for artifacts?
Converter Package Version 1.0.
Bundles wishing to use this package must list the package in the Import-Package header of the bundle's manifest. This package has two types of users: the consumers that use the API in this package and the providers that implement the API in this package.
Example import for consumers using the API in this package:
Import-Package: org.osgi.util.feature; version="[1.0,2.0)"
Example import for providers implementing the API in this package:
Import-Package: org.osgi.util.feature; version="[1.0,1.1)"
-
BuilderFactory
- The Builder Factory can be used to obtain builders for the various entities. -
ConflictResolver
- Interface implemented by a callback that can resolve merge conflicts. -
Feature
- The Feature Model Feature. -
FeatureArtifact
- An Artifact is an entity with an ID. -
FeatureBuilder
- A builder for Feature Models. -
FeatureBundle
- A Bundle which is part of a feature. -
FeatureBundleBuilder
- A builder for Feature Model FeatureBundle objects. -
FeatureConfiguration
- Represents an OSGi Configuration in the Feature Model. -
FeatureConfigurationBuilder
- A builder for Feature Model FeatureConfiguration objects. -
FeatureExtension
- A Feature Model Extension. -
FeatureExtension.Kind
- The kind of extension: optional, mandatory or transient. -
FeatureExtension.Type
- The type of extension -
FeatureExtensionBuilder
- A builder for Feature Model FeatureExtension objects. -
Features
- The Features class is the primary entry point for interacting with the feature model. -
ID
- ID used to denote an artifact. -
MergeContext
- Context provided by the caller for the merge operation. -
MergeContextBuilder
- A builder for MergeContext objects.
The Builder Factory can be used to obtain builders for the various entities.
Consumers of this API must not implement this type
The artifact ID for the bundle object being built.
Obtain a new builder for Bundle objects.
The builder.
The persistent ID for the Configuration being built.
Obtain a new builder for Configuration objects.
The builder.
The factory persistent ID for the Configuration being built.
The name of the configuration being built. The PID for the configuration will be the factoryPid + '~' + name
Obtain a new builder for Factory Configuration objects.
The builder.
The extension name.
The type of extension: JSON, Text or Artifacts.
The kind of extension: Mandatory, Optional or Transient.
Obtain a new builder for Feature objects.
The builder.
The artifact ID for the feature object being built.
Obtain a new builder for Feature objects.
The builder.
The type of entity this conflict resolver is used for.
The type of the result of the resolution.
Interface implemented by a callback that can resolve merge conflicts.
Thread-safe
The Feature Model Feature.
Thread-safe
Consumers of this API must not implement this type
Get the configurations.
The configurations.
Get whether the feature is complete or not.
Completeness value.
An Artifact is an entity with an ID.
Consumers of this API must not implement this type
A builder for Feature Models.
Not Thread-safe
Consumers of this API must not implement this type
The Bundles to add.
Add Bundles to the Feature.
This builder.
The Configurations to add.
Add Configurations to the Feature.
This builder.
The Extensions to add.
Add Extensions to the Feature
This builder.
The key.
The default value.
Add a variable to the Feature. If a variable with the specified key already exists it is replaced with this one.
This builder.
to be added.
Add a map of variables to the Feature
This builder.
Build the Feature. Can only be called once on a builder. After calling this method the current builder instance cannot be used any more.
The Feature.
If the feature is complete.
Set the Feature Complete flag.
This builder.
The description.
Set the Feature Description.
This builder.
If the feature is final.
Set the Feature is Final flag.
This builder.
The License.
Set the License.
This builder.
The Name.
Set the Feature Name.
This builder.
A Bundle which is part of a feature.
Thread-safe
Consumers of this API must not implement this type
A builder for Feature Model FeatureBundle objects.
Not Thread-safe
Consumers of this API must not implement this type
Metadata key.
Metadata value.
Add metadata for this Bundle.
This builder.
The map with metadata.
Add metadata for this Bundle by providing a map. All metadata in the map is added to any previously provided metadata.
This builder.
Represents an OSGi Configuration in the Feature Model.
Thread-safe
Consumers of this API must not implement this type
Get the Factory PID from the configuration, if any.
The Factory PID, or null
if there is none.
A builder for Feature Model FeatureConfiguration objects.
Not Thread-safe
Consumers of this API must not implement this type
The configuration key.
The configuration value. Acceptable data types are: TODO list
Add a configuration value for this Configuration object. If a value with the same key was previously provided the previous value is overwritten.
This builder.
Add a map of configuration values for this Configuration object. All values will be added to any previously provided configuration values.
This builder.
A Feature Model Extension. Extensions can contain either Text, JSON or a list of Artifacts.
Extensions are of one of the following kinds:
-
Mandatory: this extension must be processed by the runtime
-
Optional: this extension does not have to be processed by the runtime
-
Transient: this extension contains transient information such as caching data that is for optimization purposes. It may be changed or removed and is not part of the feature's identity.
Thread-safe
Consumers of this API must not implement this type
Get the Artifacts from this extension.
The Artifacts, or null
if this is not an Artifacts extension.
Get the JSON from this extension.
The JSON, or null
if this is not a JSON extension.
Get the Text from this extension.
The Text, or null
if this is not a Text extension.
The kind of extension: optional, mandatory or transient.
A transient extension contains computed information which can be used as a cache to speed up operation.
The type of extension
A builder for Feature Model FeatureExtension objects.
Not Thread-safe
Consumers of this API must not implement this type
The ArtifactID of the artifact to add.
Add an Artifact to the extension. Can only be called for extensions of type FeatureExtension.Type.ARTIFACTS.
This builder.
The Group ID of the artifact to add.
The Artifact ID of the artifact to add.
The Version of the artifact to add.
Add an Artifact to the extension. Can only be called for extensions of type FeatureExtension.Type.ARTIFACTS.
This builder.
The Group ID of the artifact to add.
The Artifact ID of the artifact to add.
The Version of the artifact to add.
The type indicator of the artifact to add.
The classifier of the artifact to add.
Add an Artifact to the extension. Can only be called for extensions of type FeatureExtension.Type.ARTIFACTS.
This builder.
The text to be added.
Add text to the extension. Can only be called for extensions of type FeatureExtension.Type.TEXT.
This builder.
Build the Extension. Can only be called once on a builder. After calling this method the current builder instance cannot be used any more.
The Extension.
The JSON to be added.
Add JSON in String form to the extension. Can only be called for extensions of type FeatureExtension.Type.JSON.
This builder.
The Features class is the primary entry point for interacting with the feature model.
Thread-safe
Consumers of this API must not implement this type
Get a factory which can be used to build feature model entities.
A builder factory.
The ID of the new feature.
The first feature
The second feature
The merge context to use for the merge operation.
Merge two features into a new feature.
The merged feature.
A Reader to the JSON input
Read a Feature from JSON
The Feature represented by the JSON
IOException
– When reading fails
ID used to denote an artifact. This could be a feature model, a bundle which is part of the feature model or some other artifact.
Artifact IDs follow the Maven convention of having:
-
A group ID
-
An artifact ID
-
A version
-
A type identifier (optional)
-
A classifier (optional)
Thread-safe
Consumers of this API must not implement this type
The group ID.
The artifact ID.
The version.
Construct an Artifact ID
The group ID.
The artifact ID.
The version.
The type identifier.
The classifier.
Construct an Artifact ID
Construct an Artifact ID from a Maven ID. Maven IDs have the following syntax:
group-id ':' artifact-id [ ':' [type] [ ':' classifier ] ] ':' version
The ID
Context provided by the caller for the merge operation.
Consumers of this API must not implement this type
The first feature.
The first bundle.
The second feature.
The second bundle.
If two merged features both contain the same bundle, same group ID and artifact ID but different version, this method is called to resolve what to do.
Return a list of bundles that should be used in this case. This could be one or both of the provided bundles, or a different bundle altogether.
The first feature.
The first configuration.
The second feature.
The second configuration.
If two merged features both contain the same configuration PID, this method is called to perform the merge operation.
The merged configuration to use.
The first feature.
The first extension.
The second feature.
The second extension.
If two merged features both contain an extension with the same IF, this method is called to perform the merge operation.
The merged extension.
A builder for MergeContext objects.
Not Thread-safe
Consumers of this API must not implement this type
Build the Merge Context. Can only be called once on a builder. After calling this method the current builder instance cannot be used any more.
The Merge Context.
The Conflict Resolver.
Set the Bundle Conflict Resolver.
This builder.
The Conflict Resolver.
Set the Configuration Conflict Resolver.
This builder.
[1]JSON (JavaScript Object Notation) https://www.json.org
[2]JSMin (The JavaScript Minifier) https://www.crockford.com/javascript/jsmin.html
[3]Apache Maven Pom Reference https://maven.apache.org/pom.html