The modular entity in an OSGi framework is referred to as a bundle. A bundle is a collection of code, resources, and configuration files that are packaged as a Java ARchive (JAR).
A bundle can be compared to a Web ARchive (WAR) in the context of a web container, or to an Enterprise ARchive (EAR) in the context of a Java Enterprise Platform. For example, a web container would inspect the contents of a WAR for configuration, resources, and code that it needs to publish the web application and manage its lifecycle.
In the OSGi world, the framework focuses on the functionality that's required to operate the bundle as an entity with a lifecycle and provides code and services. It then communicates changes to the other components in the framework and the installed bundles.
For example, as we will see in Chapter 13, Improving the Graphics, a web container installed as a bundle listens to bundles that are installed and grabs those that are identified as web applications for registration. The web container would be a service published by a bundle on the framework. In this case, both the web container and the web application are bundles installed on the framework one bundle using the other to provide a service.
Such a split of responsibilities (for example, web application publishing and lifecycle management) offers a greater flexibility in the design of a service platform. It is also applied within the framework in the organization of its components.
The components in the OSGi framework are grouped into distinct functional layers. Each layer is responsible for a specific set of tasks related to the integration of the bundle with the framework. Those layers are explained as follows:
Although we're only going to use the service layer to register our services during this case study, it is interesting to understand the functional breakdown of the framework.
The clear definition of the interfaces of the components in each layer allows for a better flexibility in the implementation of a framework, as well as a well-defined means for bundles to communicate with the service platform.
For example, a different execution environment layer would be selected depending on the target system on which the framework will run. This will happen without affecting the other layers or the bundles that are installed on the platform.
The following diagram depicts this layering and shows some of the interaction between the layers of the framework:
The bundles are kept in a sort of a sandbox and wired together, based on their declared requirements. This not only allows you to enforce a tight control over class visibility, but it also keeps track of which packages a bundle is using from other bundles. This control helps manage bundles better, resulting in the possibility to decide when a bundle can be updated without resetting the bundles that depend on it, allowing runtime update of bundles.
The lifecycle of a bundle within a framework starts with its install. A bundle can be installed either by another bundle in the framework, using the framework API, or via the framework implementation.
For example, as we will see in Chapter 5, The Book Inventory Bundle, the Felix framework provides a shell command (the install
command) that is used to install bundles. The shell service is installed as a bundle and exposes the command for use as part of the console.
Before a bundle is active on the framework, it must go through the resolution process, in which the module layer reads its manifest headers, performs required checks, and identifies the bundle's dependencies.
When a bundle is successfully resolved, it can be started and the lifecycle layer takes over the process. If a bundle activator is provided with the bundle (using the Bundle-Activator
header), then the framework will use it to activate the bundle for initialization. The framework gives control to the bundle activator through the start()
method. We'll look more closely at bundle activation in Chapter 5.
The activation can be eager or lazy
, as defined by the Bundle-ActivationPolicy
header we introduced a bit earlier.
With the eager activation policy, the bundle is activated as soon as it is done starting. When the activation policy is set to lazy
, then the bundle is only activated when the first class from that bundle is loaded.
The following state machine describes the states that a bundle can go through during its lifecycle. It also shows the actions that are performed on the bundle to shift its state.
Those states are as follows:
As we'll see in Chapter 5, by defining a bundle activator, the framework will temporarily give the bundle control of the execution flow when it is in the starting and stopping states by calling the bundle activator's start()
and stop()
methods.
Unless instructed otherwise (that is, by requesting start or stop in transient mode), the framework will keep track of whether a bundle is active and attempt to restore that state at the next startup. When the bundle is started, it is persistently marked for start.
Without going into the details of the class loading and visibility constraints, it's worth knowing that the framework keeps separate codebases for the different bundles, controlling how each bundle's classes are loaded and which classes a bundle can "see". The process of linking a bundle to provide its access to another bundle's content is called wiring.
When the framework resolves a bundle for installation, it reads the bundle manifest looking for its capabilities (the packages it provides or exports) and its requirements (those that it imports). It uses this information to wire the bundles together in a mesh of dependencies, thus constructing the class space visible to each bundle.
This mechanism allows each bundle to clearly define which of its packages (and classes) are hidden from other bundles and which are shared.
For example, if Bundle B exports package b
and Bundle C exports package c
, then those packages are made available for bundles that require them on the framework.
Here, Bundle A imports packages b
and c
. Those bundle capabilities and requirements are expressed in the form of the Import-Package
and Export-Package
OSGi headers that we'll see in a bit.
The preceding diagram is a simple view of this wiring. The wires are actually a bit more complex and keep track of constraints such as dependency version ranges, optional dependencies, and so on.
If Bundle C were not installed and package c
is not provided by another bundle, then Bundle A cannot be resolved successfully because of its missing dependency.