Typical OSGi applications are assembled out of bundles and configured through both, OSGi configurations and framework properties (though these less frequently used than OSGi configurations). Depending on the nature of the application, there might be additional artifact types involved.
While bundles already provide a good way to define rather small, coherent modules, there is often a need to distribute or provision a set of such bundles together with some configuration. OSGi Deployment Admin and OSGi subsystems are two ways of trying to solve this issue. The feature model of Apache Karaf and the provisioning model of Apache Sling are two other approaches.
The goals of this proposal are:
The feature model should at least meet the following requirements:
The prototype uses JSON as a well defined and understood format. This fits nicely with the new OSGi R7 JSON format for configurations.
A model file describes a feature. A feature consists of:
Notes for users of Apache Sling's provisioning model:
A feature has a unique id. Maven coordinates (https://maven.apache.org/pom.html#Maven_Coordinates) provide a well defined and accepted way of uniquely defining such an id. The coordinates include at least a group id, an artifact id, a version and a type/packaging. A classifier is optional.
While group id, artifact id, version and the optional classifier can be freely choosen for a feature, the type/packaging is defined as “osgifeature”.
TBD: Is “osgifeature” a good type?
Maven coordinates are used to define the feature id and to refer to artifacts contained in the feature, e.g. bundles, content packages or other features. There are two supported ways to write down such a coordinate:
In some cases only the coordinates are specified as a string in one of the above mentioned formats. In other cases, the artifact is described through a JSON object. In that case, the id property holds the coordinates in one of the formats.
In order to avoid a concept like “Require-Bundle” a feature does not explicitly declare dependencies to other features. These are declared by the required capabilities, either explicit or implicit. The implicit requirements are calculated by inspecting the contained bundles (and potentially other artifacts like content packages ).
Once a feature is processed by tooling, the tooling might create a full list of requirements and capabilities and add this information in a special section to the final feature. This information can be used by tooling to validate an instance (see below) and avoids rescanning the binary artifacts. However this “cached” information is optional and tooling must work without it (which means it needs access to the binaries in that case). TBD the name and format of this information.
The JSON feature object has the following properties:
The JSON feature object might contain a bundles property holding a JSON array with the bundles contained in this feature. The values in this array can either be a string holding the coordinates for the bundle or a JSON object if additional properties need to be specified. The JSON object supports the following properties:
TBD: The implementation currently uses the map based configuration. It needs to change.
Includes allow an aggregation of features and a modification of the included feature: each entity listed in the included feature can be removed, e.g a configuration or a bundle. The list of includes must not contain duplicates (not comparing the version of the includes). If there are duplicates, the feature is invalid.
Once a feature is processed, included references are removed and the content of the included features becomes part of the current feature. The following algorithm applies:
While includes must not be used for assembling an application, they provide an important concept for manipulating existing features. For example to replace a bundle in an existing feature and deliver this modified feature.
An extension has a unique name and a type which can either be text, JSON or artifacts. Depending on the type, inheritance is performed like this:
A feature itself has no special support for environments (prod, test, dev). In practice it is very unlikely that a single file exists containing configurations for all environments, especially as the configuration might contain secrets, credentials, urls for production services etc which are not meant to be given out in public (or to the dev department). Instead, a separate feature for an environment can be written and maintained by the different share holders which adds the environment specific configuration. Usually this feature would include the feature it is based on.
Each bundle needs to be explicitly assigned to a start level. There is no default start level as a default start level is not defined in the OSGi spec. In addition, it is a little bit confusing when looking at the model when there is a list of bundles without a start level. Which start level do these have? It is better to be explicit.
However as soon as you have more than one feature and especially if these are authored by different authors, start level handling becomes more tricky. Assigning correct OSGi start levels in such scenarios would require to know all features upfront. Therefore this start level information is interpret as follows: instead of directly mapping it to a start level in the OSGi framework, it defines just the startup order of bundles within a feature. Features are then started in respect of their dependency information. Even if a feature has no requirement with respect to start ordering of their bundles, it has to define a start level (to act as a container for the bundles). It can use any positive number, suggested is to use “1”. Bundles within the same start level are started in any order.
In most cases, configurations belong to a bundle. The most common use case is a configuration for a (DS) component. Therefore instead of having a separate configurations section, it is more intuitiv to specify configurations as part of a bundle. The benefit of this approach is, that it can easily be decided if a configuration is to be used: if exactly that bundle is used, the configurations are used; otherwise they are not.
However, there might be situations where it is not clear to which bundle a configuration belongs or the configuration might be a cross cutting concern spawning across multiple bundles. Therefore it is still possible to have configurations not related to a particular bundle.
In fact, configurations - whether they are declared as part of a bundle or not - are all managed in a single set for a feature. See above for how includes etc. are handled.
This is a feature example:
{ "id" : "org.apache.sling:my.app:feature:optional:1.0", "includes" : [ { "id" : "org.apache.sling:sling:9", "removals" : { "configurations" : [ ], "bundles": [ ], "framework-properties" : [ ] } } ], "requirements" : [ { "namespace" : "osgi.contract", "directives" : { "filter" : "(&(osgi.contract=JavaServlet)(version=3.1))" } } ], "capabilities" : [ { "namespace" : "osgi.implementation", "attributes" : { "osgi.implementation" : "osgi.http", "version:Version" : "1.1" }, "directives" : { "uses" : "javax.servlet,javax.servlet.http,org.osgi.service.http.context,org.osgi.service.http.whiteboard" } }, { "namespace" : osgi.service", "attributes" : { "objectClass:List<String>" : "org.osgi.service.http.runtime.HttpServiceRuntime" }, "directives" { "uses" : "org.osgi.service.http.runtime,org.osgi.service.http.runtime.dto" } } ], "framework-properties" { "foo" : 1, "brave" : "something", "org.apache.felix.scr.directory" : "launchpad/scr" }, "bundles" : { "1" : [ { "id" : "org.apache.sling:security-server:2.2.0", "hash" : "4632463464363646436" }, "org.apache.sling:application-bundle:2.0.0", "org.apache.sling:another-bundle:2.1.0" ], "2" : [ "org.apache.sling:foo-xyz:1.2.3" ] }, "configurations" { "my.pid" { "foo" : 5, "bar" : "test", "number:Integer" : 7 }, "my.factory.pid~name" { "a.value" : "yeah" } }
There are two major differences between a repository as described in the Repository Service Description and the feature model. A repository contains a list of more or less unrelated resources whereas a feature describes resources as a unit. For example a feature allows to define a bundle together with OSGi configurations - which ensures that whenever this feature is used, the bundle together with the configurations are deployed. A repository can only describe the bundle as a separate resource and the OSGi configurations as additional unrelated resources.
The second difference is the handling of requirements and capabilities. While a repository is supposed to list all requirements and capabilities of a resource as part of the description, the feature model does not require this. As the feature model refers to the bundle and the bundle has the requirements and capabilities as metadata, there is no need to repeat that information.
By these two differences you can already tell, that a repository contents is usually generated by tools while a feature is usually a human created resource. While it is possible to create a repository index out of a feature, the other way round does not work as the repository has no standard way to define relationships between resources.
The feature model does not allow to explicitly list requirements or capabilities for artifacts. An artifact, for example a bundle, contains this information as part of its metadata. However, to calculate or display these, the tool processing the feature needs to have access to the artifact and needs to extract this. While in general this does not pose a problem by itself, it might happen that the same artifact is processed several times for example during a build process, causing overhead.
To avoid this, a feature might contain an additional section, named “reqscaps” (TODO find a better name). This section is grouped by artifact ids and contains the requirements and capabilities of each artifact. While the requirements and capabilities of a single artifact must be correct and neither leave out or add additional ones, the list of artifacts must not be complete. Tooling will first look into this section to get requirements and capabilities for an artifact. If there are none, it will process the artifact.
{ ... "reqscaps" : { "org.apache.sling:org.apache.sling.scripting.jsp:1.0.0" : { "capabilities" : [], "requirements" : [] } }
An application jar can contain a set of features (including the listed artifacts).
An optional application configuration further defines the possibilites:
{ "features" : [ "org.apache.sling:org.apache.sling.launchpad:10" ], "options" : [ "org.apache.sling:org.apache.sling.scripting.jsp:1.0.0", { "id" : "org.apache.sling:org.apache.sling.scripting.htl:1.0.0", "tag": "htl" } ], "defaults" : { "auto-add-options": true, "tags" : ["htl"] }, "framework" : { "id" : "org.apache.felix:org.apache.felix.framework:5.6.4" } }
Such a configuration is required for an application, at least one feature needs to be listed in either the features or the options section. All features listed in the features section will be added to the application, the ones listed in options are optional and depending on the settings and user input will either be added or left out. In addition all available features of an application will be used to make the application runnable (resolvable).
The documentation for Apache Sling's provisioning model can be found here: https://sling.apache.org/documentation/development/slingstart.html