When the rubber finally meets the pavement, we usually find many complicated and awkward problems. Differences in hardware, operating system configurations, or setup information can bring our carefully designed and crafted software to a halt in an unexpected environment. Other common problems are versions that don't match, missing packages or libraries, different names for environment properties, and so on.
Most of us who have had to write installation programs or scripts are aware of the sometimes embarrassing amount of time needed to get an already-working software system operational on different target machines. I remember a colleague who promised to eat a floppy disk if the installation script failed one more time, and was consequently served a tossed floppy salad a few hours later...with dressing.
Meet the OSGi Service Platform, Release 2 specification. This specification is the result of two years of intensive collaboration between OSGi members (Sun, IBM, Ericsson, Nokia, Motorola, Oracle, Telcordia, HP, and 70 others). It has already been implemented by several vendors, and development versions can be downloaded from Gatespace, IBM, ProSyst, Sun, and others. It allows independent application developers to develop software that can be remotely managed for different standalone computers, processors, and operating systems of various sizes and configurations. This list includes, but is definitely not limited to, embedded computers.
The specification relies heavily on Java, the obvious choice when the final platform can't be restricted to Microsoft Windows hardware requirements, or where high reliability and availability is needed. On top of Java, the specification defines a framework that addresses the needs of a platform that's running continuously, and must run a dynamically changing set of applications, and react directly - without supervision - to changes in its environment.
On top of the framework, the OSGi has defined a number of services that can be used by applications. This rapidly expanding list contains the following services:
Execution environment: One or more definitions of what classes and methods minimal environments should support.
This list will expand significantly in the near future. OSGi has many expert groups working in certain areas. These groups adopt existing standards or develop derived specifications that add to the list of available services. Other groups are working on adopting OSGi specifications or developing their own specifications for vertical markets. The OSGi is open and invites companies to participate in this specification process (see www.osgi.org).
Applications can also use standard Java libraries. These libraries can be wrapped in the application's delivery file or referred to from the manifest file. Versioning issues are explicitly managed by the framework.
Figure 1 shows how the different parts are related. It also shows that bundles (the OSGi applications) can use the OSGi Framework and all standard Java libraries, and access the operating system with the underlying hardware.
A key aspect of the OSGi is that there are numerous vendors (e.g., Gatespace, IBM, ProSyst, and Sun) that implement the specifications while they're being developed. In addition, the OSGi expert groups develop a reference implementation and test suites for each specification. This means that the specifications are practical, usable, and have multiple (choice!) industrial-strength implementations available (most of the vendors actually have free downloadable SDKs for evaluation purposes). An open source implementation called Oscar (that still has some work to do, especially to match release 2) can be found at http://oscar-osgi.sourceforge.net.
The framework is the part that changes a Java Virtual Machine from a single application environment into a multiple one. The advantages are many: running multiple applications in a single VM means fewer process swaps, fast interapplication communication, and significantly less memory consumption. Protection between the applications is provided by the Java runtime and the framework.
The concept of a bundle is used to represent these multiple applications. A bundle is a Java ARchive (JAR) file containing "parts" that are needed to run the application, and a manifest file declaring the parts that should be available when the application is started. These parts are provided by other bundles or by the environment. Dependencies are fully managed by the framework. When bundles are started, they usually register and get services. Services are Java objects defined by a Java interface that can be found in the framework service registry. Bundles can communicate only through these services (see Figure 2).
An important concept of the OSGi specifications is the so-called "management bundle." This is a bundle with administrative privileges. Management bundles are required because the framework is "policy free," i.e., it provides many mechanisms but carefully tries to abstract itself from decision-making. These decisions, the policies, are provided by the management bundle and will vary from installation to installation. Management bundles are responsible for managing the environment. Many different types exist: command-line consoles, remotely managed SNMP-based bundles, Web-based interfaces, and static configuration-based management bundles.
One of the management bundle's many responsibilities is installing bundles. An API is provided that gives the framework a Java InputStream object and a name. The InputStream is connected to a JAR file that contains the classes and resources that are provided by that bundle. This fits seamlessly with the Java URL and file system model. Uninstalling doesn't require a complicated uninstall script - the framework is fully aware of all the aspects that are part of the bundle and can completely clean up after itself. It even cleans up the private files that were generated by the bundle. Updating the bundle is just as simple.
Once a bundle is installed it needs to be started. However, there are dependencies specified in the JAR file that need to be resolved. These dependencies are about packages. Java classes are always contained in a package. This is the first part of the name of the class (until the last "."), e.g., the package of java.lang.String is java.lang.
A bundle can use packages in three different ways. First, it can have private packages. All classes of the bundle that don't need to be shared with other bundles should be private. Different bundles can thus include the same packages.
Second, classes that need to be shared with other bundles should be exported. Interfaces and classes that are used to communicate (e.g., the interfaces that define the services) must be exported or else the bundles will run into ClassCastExceptions when they exchange objects. Any bundle can specify any package for export. The framework will ensure that only one of the bundles at any time will export a specific package.
Third, the bundle can also specify that it needs certain packages; for example, that the classes in its JAR file refer to the javax.servlet and javax.servlet.http packages. Imports require an associated export. The framework will pick only one bundle to export a specific package at any time to prevent ClassCastExceptions. The decision of which bundle to pick for export is further guided by the versions (all package references can contain a version specifier) and security.
Figure 3 shows how packages can be imported from the framework (or standard Java classpath) or other bundles, or exported. The resolving of these packages is under the control of the framework and may be subject to security checks.
Bundles can also include other JAR files. The manifest can be used to tell the framework that this JAR file contains a library that must be available on the classpath. For example, a bundle could include the servlet.jar JAR file and then export the javax.servlet and javax.servlet.http packages. Managing the classpath is one of the framework's most important tasks. Some people have said that the OSGi specifications deliver the original promise of classloaders and classpaths.
The JAR files can also contain native libraries. These libraries are not restricted to a single hardware/OS architecture. The manifest can contain a specification of which libraries are intended for which architecture. The framework will find the set of possible libraries and load the best-fitting one. The framework will handle all complicated library path issues that normally plague the deployment of native code.
Starting the Bundle
After the dependencies are resolved and the right bundles are picked to export, the bundle needs to be started. For this reason, the manifest contains a header that points to a special class: the activator. An object of this class is created and cast to a BundleActivator. This interface contains a start method and a stop method that are used to start and stop the bundle. For efficiency reasons, these methods should return quickly or they'll block the system. They normally start a background thread or get/register some services. The stop method cleans up all the parts that the framework can't directly clean up and stops any threads.
A started bundle is expected to provide some utility to the end user. It's highly likely that it's using services to provide this functionality to users and other bundles. The services - Java objects implementing a service interface - are available from the framework registry. This registry is searchable with a simple but very expressive filter. The syntax of this filter is derived from the LDAP filter syntax RFC-1960. This type of filter can have comparisons for equality (=), magnitude (<=, >=), substring (*x*), presence (*), and approximate matches (~=). Expressions can be AND (&), OR (|), NOT (!), and nested to any depth.
( & (service.pid=USB-1232312452)( | (vendor~=ericsson)(vendor~=ibm) ) )
This filter language is used to search the framework, to prefilter events before they're delivered, and, in many service specifications, to find objects.
The properties that are used in a search are given to the framework when a service is registered. Some properties (like the interfaces that the service implements) are automatically set by the framework, some properties have defined semantics (like "service.pid" is a unique identifier for a service), and other properties are defined by the bundles themselves. Each property can, as with LDAP, have multiple values; for example, a device needs to be registered with a property DEVICE_CATEGORY. However, a device that fits multiple device categories can put the categories in an array or a vector object.
A cornerstone of the OSGi architecture is dynamism. Bundles can be installed, updated, started, stopped, and uninstalled at any time; devices can come in any range and be represented in the framework registry at any time. This makes the environment extremely dynamic. Therefore, the framework offers a comprehensive model for writing applications for such an environment. All important changes to the environment are sent to listeners that have specified an interest in these events.
Installation, start, stop, and update of bundles is sent out as bundle events. Management bundles can use these events to apply their policies (for example, the security settings or installing required support bundles) at the appropriate time. Service registrations, unregistrations, and modification of service properties are sent as service events (see Figure 4).
Bundles use these events to adapt to the changes in the environment. For example, when a digital camera is plugged into a FireWire network, a device service representing this camera is registered. A bundle that controls a monitor can then react to this registration and add this new camera to the monitor's menu. A special class, the ServiceTracker, is provided to simplify this process of tracking services in the registry, which makes using this dynamic model trivial.
Though the dynamism undoubtedly introduces a certain amount of complexity into the programming, the benefits are huge. OSGi-based environments don't have to be rebooted when configuration changes take place or bundles are installed and updated. The environment adapts itself to the bundles, devices, and services available - a must for a standalone, continuously running server.
Running a number of bundles together in a single VM and sharing the resources is not always easy; therefore, the framework closely tracks the dependencies between the bundles. When bundles are stopped or uninstalled, the framework will use its extensive knowledge of these dependencies to clean up as much as possible. Services registered by a bundle are automatically unregistered and services used from the framework are returned. This significantly minimizes the complexity of sharing a VM.
The framework has been targeted to run multiple independent bundles simultaneously. It was assumed from the beginning that these bundles couldn't all be trusted in such an environment; therefore, security is paramount. That's the reason the OSGi has adopted the Java 2 security model. In this model, a certain "type" of security is represented by a Permissions class. For example, access to the file system is guarded by the FilePermission class. These permissions are associated with the code base, i.e., the place where the code came from. When a permission needs to be verified, the checker creates an instance of the appropriate Permission class and calls the SecurityManager with this object. This will assure that the stack is crawled, and each caller is checked to see if it has a Permission object. For example, when a file is opened, the Java IO runtime system creates a FilePermission object with the filename as a parameter and with READ as the action. The caller (and all its callers) are then checked to see if its set of permissions include a FilePermission object that implies the given filename and action.
Normally, Permission objects are associated with code bases through the Policy class. The default policy reads the permissions from a file. Permissions can normally be granted on a file basis or when certificates are used on the signer basis. This is so flexible that it's sometimes quite confusing.
Therefore, the OSGi has simplified the model significantly by granting permissions only on a per-bundle basis. As is customary in the OSGi specification, the mechanism to associate the permissions with the bundle are made in such a way that an operator can use his or her own policies, bought from network management vendors or rolled by the operator's staff.
Management bundles have two mechanisms available for permission-handling. One, there's a special service, PermissionAdmin, that permits a management bundle to set, get, and reset the bundle and default permissions. The permissions are associated with the location (this is normally the URL where the bundle comes from). This makes it possible to set the permissions before the bundle is actually downloaded in the environment. However, there's also the possibility of just-in-time permission-setting.
Two, a management bundle can register a SynchronousBundleListener with the framework. This listener is called when the bundle is installed but before the bundle has had a chance to do something (including exporting code). At this moment, the management bundle can look up the permissions and set them.
As always, using secure systems doesn't make life easier for the developer, but at least the OSGi environment makes the Java 2 security model significantly less painful to use in practice.
The framework introduces three new permission types: ServicePermission, PackagePermission, and AdminPermission.
AdminPermission gives the owner the authority to manage the framework. Management bundles require AdminPermission to perform their numerous duties. This AdminPermission is a broad permission class intentionally. Administering and configuring large-
scale installations is complex and error-prone. Therefore, these tasks should be minimized as much as possible. Having one broad permission for administrative tasks was deemed more than sufficient and significantly reduces the administration of permissions.
Bundles can import and export Java packages as explained earlier. This is obviously a security risk because malicious bundles could export an innocent-looking package that's inadvertently used by an important bundle. Also the reverse, a bundle importing a package that it might use to do "evil" things should be under strict operator control. These threats are mitigated with the PackagePermission class. Package-
Permission classes can hold the name of a package (including wildcards) and the action export or import.
As usual, some bundles are more equal than others and the ServicePermission class is intended to differentiate between them. Bundles can be given permission to register or get a specific service (or a wildcard). Conforming frameworks must ensure that when a bundle doesn't have this permission, it won't be able to detect the existence of such a service. For example, when a bundle registers an HTTP service object with the framework, another bundle that doesn't have ServicePermission(HttpService,GET) won't even be able to see the registration event of that service.
This is a powerful mechanism that's broadly used in the OSGi specifications to raise firewalls between bundles. For example, in the configuration management specification there are two parties: the Configuration Admin service that configures clients and the clients that are configured. Both are represented as services in the framework service registry. However, registering a Configuration Admin service is a privileged action, reserved for the bundle designated by the operator. On the other hand, registering as a client is allowed by basically every bundle. Getting the client service is again privileged and getting the Configuration Admin service is not.
It turns out that many security schemes don't require special Permission classes, but can leverage the expressive power of ServicePermission.
The framework, though initially targeted at residential gateways, has already found a wider audience. The car industry is showing great interest in the OSGi specifications and is currently participating in continuing the effort. The applicability of the specifications is even wider than gateways in cars or homes. It can be used to deploy applications in almost any environment where they consist of multiple components that, together, form the needed functionality. The ease of remote management, a well-defined environment, numerous available tools, security, many industrial-strength SDKs, and the dynamic collaborative environment make it interesting for many software deployment problems, from PC-based applications to high-end Unix application servers, from servlet-based applications to cellular network base station controllers somewhere out in the countryside. Why not give it a try!
Peter Kriens, an OSGi technical officer, also worked for Ericsson Research in Stockholm, where he got involved with home servers and residential gateways. Peter studied electronics in Alkmaar, Holland.