Beanome: A Component Model for the OSGi Framework 1 Introduction

Beanome: A Component Model for the OSGi Framework 1 Introduction
Beanome: A Component Model for the OSGi Framework
Humberto Cervantes and Richard S. Hall
Laboratoire LSR Imag, 220 rue de la Chimie
Domaine Universitaire, BP 53, 38041 Grenoble, Cedex 9 France
{humberto.cervantes,richard.hall}@imag.fr
Abstract:
The Open Services Gateway Initiative (OSGi) specification defines a service-oriented framework for
use in residential gateways. In this context, the OSGi framework acts as a gateway from the Internet
to consumer devices attached to the residence's home-area network. Service providers use the
gateway to deliver products and services to end-users, such as home security or health care
monitoring. As more powerful consumer devices are introduced, the devices themselves become
mechanisms for delivering services to the framework. This level of sophistication results in complex
applications and the need for sophisticated mechanisms to simplify creating them. This paper
introduces Beanome, a lightweight layer implemented on top of the OSGi framework, that implements
a simple component model and framework for creating OSGi applications. Beanome is not a
component model for consumer devices per se, but is a component model for the OSGi framework,
which itself is used to create applications that access and provide user-level and management-level
interfaces to consumer devices.
Keywords: OSGi, lightweight, component model, component framework
1 Introduction
For software developers, consumer device technology provides an increasingly interesting
environment in which they can deliver products and services. Consumer devices are much
more prolific than personal computers and, perhaps more importantly, are more integrated
into everyday life. As a result, software developers benefit from unprecedented levels of
access to the user by targeting their products and services towards consumer devices. The
consumer device software market is fueled by four factors:
•
The increasing power of consumer devices, such as mobile phones or personal digital
assistants (PDAs).
•
The transformation of “traditional”consumer devices, such as refrigerators and
washing machines, into “smart”consumer devices.
•
The widespread availability of broadband Internet connectivity.
•
The growing prevalence of home-area networks.
The Open Services Gateway Initiative (OSGi) is helping developers realize the potential of
the consumer device market; OSGi is an “independent, non-profit corporation working to
define and promote open specifications for the delivery of managed broadband services to
networks in homes, cars, and other environments.” [4] Specifically, the OSGi specification
defines a service platform [5] that includes a minimal component-like model and a small
framework for managing the components, including a packaging and delivery format.
The OSGi framework facilitates dynamic installation and management of simple components
called bundles. These dynamically loadable components interact with each other and form
“applications” by providing and using services. A service is a Java interface with defined
semantics and potentially multiple implementations. Services are packaged along with their
associated resources and deployed via the internet into the OSGi home services gateway. In
this scenario, consumer devices become delivery mechanisms, capable to deliver bundles
along with their associated services.
The services gateway acts as the access mechanism or conduit between service providers and
the end-user. A typical application scenario is that of a home security company that monitors
the end-user's home for security concerns using a combination of hardware (e.g., sensors) and
software (e.g., sensor monitors, software control panels for the end-users and the service
provider). While OSGi creates a good foundation on which to build such services, it is still
fairly low-level; the low abstraction level increases the complexity of offering complex
applications on top of the OSGi framework.
This paper investigates the limitations of the OSGi Service Platform specification with
respect to using it to build complex applications. Beanome, a lightweight component model
and framework implemented on top of the OSGi platform, is introduced as a means to
improve support for complex OSGi applications. The paper begins with an overview of the
OSGi Service Platform and is followed by a discussion of the limitations of OSGi. Then the
Beanome component model, framework, and mapping to OSGi are presented. Current status
and future work follow, along with related work and the conclusion.
2 The OSGi Service Platform
The OSGi Service Platform is comprised of two parts, the OSGi framework and the OSGi
standard services. For the purposes of this paper, the framework is the most important since it
defines what a service is, whereas the standard services define specific services and their
specified functionality. As such, the standard services, which include HTTP, logging, device
access, and user management among others, are not discussed further. It is sufficient for the
purposes of this paper to understand that services in OSGi are simply Java interface
definitions that have precise and specified semantics; the semantics of a service interface are
defined by the creator of the service interface and any object that implements a service
interface is assumed to obey its contract.
The OSGi framework creates a host environment for managing bundles and the services they
provide; a bundle is the physical unit of deployment in OSGi and is also a logical concept
used by the framework for organizing its internal state. Concretely, a bundle is a Java JAR
file that contains a manifest and some combination of Java class files, native code, and any
associated resources. An installed bundle in the framework is uniquely identifiable by either
its bundle identifier, a number assigned dynamically by the framework when the bundle is
installed, or by its location, which is an arbitrary character string used when installing the
bundle. In common practice the location string of a bundle is typically a URL, but the
specification does not define it as such; since bundles are uniquely identified by their location
string, it is not possible to install two bundles from the same location.
The management mechanisms provided by the framework enable bundle installation,
activation, deactivation, update, and removal. Logically, installed bundles have an associated
state that at any given time is one of the following values: installed, resolved, starting, active,
stopping, or uninstalled. The state of active bundles is persistent across framework
activations, meaning that active bundles are returned to the active state when the framework
is shutdown and subsequently restarted.
The bundle-to-JAR file mapping is one-to-one and meta-data about the bundle is available in
the manifest file of the JAR file; the manifest file corresponds to the entry in the JAR archive
named META-INF/MANIFEST.MF. The manifest file is simply a set of attribute-value pairs
where some attributes are standardized by the OSGi specification. The standard manifest
attributes describe the class path for the bundle, any exported Java packages provided by the
bundle, any imported Java packages required by the bundle, native library requirements, and
information intended for humans (e.g., bundle name and description).
Besides the aforementioned attributes, the manifest may also contain an attribute that
specifies an activator class for the bundle. The activator class plays an important role since it
is the mechanism by which bundles are able to get a context for accessing framework
functionality. The activator class is called when the bundle is started or stopped and, in both
instances, the bundle is given its specific context for accessing the framework. The context
allows the activator or any other class that has access to the context to look up services in the
framework's service registry, register services of its own, access other bundles, and install
additional bundles. Bundles are not required to have an activator class; it is possible that a
bundle is only a library of Java packages and does not need to access the framework.
Services are the main building blocks for creating applications in the OSGi framework.
Services are specified as a Java interface and are implemented by a separate Java class and its
associated classes and resources, all of which are packaged as a bundle (i.e., a JAR file with a
manifest). The service interface definitions are exported by the defining bundle so that other
bundles that want to use the service can import them; the service implementations remain
private.
A bundle implementation may request services from other bundles, register services to be
used by other bundles, or both. A service is registered with the framework by specifying the
service's interface name, an instance of a class that implements the service interface, and an
optional set of attribute-value pair properties. To find a service, a bundle performs a simple
LDAP query on the framework's service registry; it is sufficient to query using only the
service interface name, but a query can also include references to the associated service
properties. A service query returns zero or more matching services.
3 OSGi Limitations
The scope of the OSGi specification is focused on defining a services gateway; the OSGi
specification does not purport to be a sophisticated component model for developing complex
applications. As such, the limitations discussed in this section are not shortcomings of OSGi
per se, but result from the fact that developers will invariably try to build complex
applications with it.
3.1 Dependencies
In OSGi, three types of dependencies are present:
•
Bundle-to-package: classes inside of a bundle may need to import code that is
external to their JAR file; in this case, the bundle must explicitly import the code (in
the form of Java packages). Other bundles can explicitly export Java packages, which
are then used to satisfy import requirements. If the framework is unable to fulfill a
bundle's import requirements, then the importing bundle cannot be started. Package
dependencies are declared in the manifest file.
•
Bundle-to-service: classes inside of a bundle may use registered services by querying
for them in the framework service registry. Unlike package dependencies, service
dependencies are not guaranteed by the framework; this means that a bundle can be
started, even if the services that it requires are not available. Service dependencies
may be declared in the manifest file, but currently the OSGi specification indicates
that this is for information purposes only.
•
Service-to-service: this is a special case of a bundle-to-service dependency, where the
dependency is between a specific service implementation and an external service,
rather than between the bundle as a whole and an external service. In this particular
case, a bundle may have several service implementations, some of which have their
dependencies satisfied and some of which that do not. Service-to-service
dependencies are not covered by the OSGi specification.
Bundle-to-package dependencies are handled fairly well within the OSGi framework, which
even provides simple version control support (where backwards compatibility is assumed).
Neither bundle-to-service nor service-to-service dependencies are handled by the OSGi
framework, even though bundle-to-service dependencies are described in the bundle
manifest.
Since bundle-to-service and service-to-service dependencies are implicit in OSGi, they are
not externally manageable. Instead, the details of these dependencies are buried inside of
their respective bundle and service implementations, which results in OSGi applications that
are difficult to deploy and manage. For example, it is not possible to create a service to
automatically install all required services for a bundle nor is it possible to allow the user to
externally configure service dependencies if he or she wants to use a specific service
implementation.
Bundle-to-package and bundle-to-service dependencies are fairly easy to understand, but
service-to-service dependencies are more complicated. A service-to-service dependency is
not a dependency between two services, rather it is a dependency between a specific service
implementation and another service interface. In other words, services themselves do not
have dependencies because they are only interfaces and do not specify an implementation.
Only implementations of a service can have dependencies and multiple implementations of
the same service may have different dependencies.
There are multiple, independent factors that characterize a service-to-service dependency;
two important factors are cardinality and dynamism. Cardinality is useful for expressing
optionality, such as a zero-to-one dependency, and aggregation, such as a one-to-many
dependency. Dynamism indicates whether a dependency tracks changes in the environment at
runtime, such as the arrival of new services that satisfy the dependency constraints. An
example of a dynamic, zero-to-many dependency occurs in a situation where an application
offers a “plugin” mechanism. In this scenario, the application can function without any
plugins and it can integrate new plugins as they arrive in the environment.
Notice that bundle-to-bundle dependencies are not present in OSGi, in fact, they are
essentially forbidden. This means that it is not possible to depend on the packages or services
of a specific bundle and the framework is free to resolve these issues as it sees fit.
3.2 Application-level
Conceptually, an “application” in the OSGi framework is the transitive closure of all
dependencies among packages, bundles, and services. As the previous subsection points out,
OSGi handles package dependencies, but does not handle dependencies among bundles and
services or among services implementations. As a result, OSGi not have an explicit notion of
an application nor does it define any application-level services, such as automatic application
deployment.
3.3 Complexity
An important aspect of the service-oriented paradigm of OSGi is that services may appear or
disappear at any time. This means that an application, i.e., a set of bundles connected via
service dependencies, must be able to handle this dynamic situation. As a result, all bundles
must implement complex code to handle binding and unbinding of services as they
dynamically arrive and depart, respectively.
4 Beanome
Beanome adds a thin layer on top of the standard OSGi framework to provide functionality
similar to that found in existing component models [9] so that applications are built out of
assemblies of instances of components [see Figure 1]. Beanome defines a component model
and a component framework to support the model. The following subsections describe the
characteristics of Beanome components, the framework, and their mapping onto the OSGi
framework.
Figure 1. Layers introduced by Beanome.
4.1 Beanome Component Model
The Beanome component model is based on the concept of component types. Component
types are identified by a unique name and have an associated version number. The structure
of a component type is defined in an XML file, called a component descriptor file, that
extends the OSGi manifest. Several components may be described in a single component
descriptor file. A component type, as described by its component descriptor, is used as a
template for creating component instances.
The simplest possible Beanome component type consists of an interface and a Java class that
implements the interface. In this context, “interface” is abstract and refers not only to a Java
interface, but may also refer to a Java class. A component type may provide more than one
interface, where each interface is implemented by a different class. When multiple
implementations exist, it is possible to bind the separate implementations. Figure 2 is an
example of a component descriptor for a simple component that provides two interfaces
where each is implemented by a separate Java class and there is a binding between the two
implementations.
!
" #
$
"
"
&
%
%
%
'
(
Figure 2: Simple component type that provides two interfaces.
Components types may need to create instances of other component types within their
implementation; in this case, the component must define a requires clause for the required
component. An interface provided by the required component must also be specified. Any
required component dependency must be resolved before the component type can be
instantiated. These types of dependencies result in a hierarchy where one component type is
built up out of instances of other component types and so on. The top-level of such a
hierarchy results in a Beanome application that is itself a component type that may be re-used
in another application.
Component types may also have properties associated with them that are used for
customization purposes. The types of these associated properties are primitive, such as
integer, boolean, and string. These properties are per component type, not per component
instance; similar to static members in a Java class.
Sometimes it is inconvenient to define all component interface implementations and/or all
interface implementation bindings directly in the component descriptor file; for example,
selected interface implementations may depend on property values and the subsequent
bindings may depend on the chosen interface implementations. While it is possible to extend
the component descriptor format to describe such a complicated scenario, the end result
would be a cumbersome and inefficient mini-programming language. Instead, the component
descriptor format allows interface implementation selection and implementation binding to be
delegated to a Java class. Figure 3 describes a complex component that has provided
interfaces, required interfaces, properties, and delegated implementation selection and
binding.
"
!
)
" #
$
#
*
(+ #
,- ,
"
"
*
(+ #
.,
..
"
"
"
"
/
0
Figure 3. A Complex component which delegates implementation selection and binding.
4.2 Beanome Component Framework
The Beanome component framework provides the necessary runtime functionality to support
the Beanome component model. The component framework, called the runtime, introduces
two main concepts: component factories and a component registry. The component registry
maintains a list of all available component factories that are registered with the runtime. A
component factory is associated with a specific component type and implements the factory
method pattern [2] for creating component instances. Component factories also provide finder
methods to lookup shared component instances, where each component instance is identified
by a unique, “well-known” name. The runtime provides access to the component registry and
to introspection mechanisms on component types and instances.
To create a component instance, the runtime retrieves the component factory for the desired
component type from the component registry. The runtime then creates all of the objects that
implement the component and that are specified in the component descriptor. For
instantiation to be successful, the runtime must also locate factories and create instances for
component types that are required (i.e., there is an associated “requires” clause in the
component descriptor). If the runtime is unable to find factories for required interfaces, then
instantiation will fail.
Using a factory to find a component instance is similar to creating a component instance; the
runtime performs a lookup on the component type registry to find a component factory then
calls its finder method with the “well-known” name of the desired instance. If the instance
does not already exist, the component factory creates the instance on-the-fly. The purpose of
instance lookup is to enable component instance sharing.
4.3 Mapping to OSGi
The Beanome runtime is implemented as an OSGi bundle. If a component interface
implementation requires access to the runtime, it must implement an interface called
RuntimeClient; this allows the implementation to receive a reference to the runtime
when it is instantiated.
Bundles are used to package one or more Beanome component types. These component type
bundles must register their component factories in the OSGi service registry. To simplify this
process, a generic bundle activator is exported by the Beanome runtime bundle. The generic
activator parses the component descriptor file and automatically registers a
ComponentFactoryService interface for each component type in the file. Factory
service registration takes place even if the required dependencies of the component type are
not fulfilled, because interface implementation resolution does not take place until
instantiation.
When the generic activator registers factories, it uses the standard OSGi mechanism to attach
to each factory service a set of properties that include the name of the component type, the
type's associated version number, and the set of interfaces that the component provides. The
list of provided interfaces is necessary because the runtime or another component instance
can request a component factory whose component type provides a given set of interfaces,
regardless of its name or version.
If a component type requires an interface implemented by another component (i.e., there is a
“requires” clause in the component descriptor), then these dependencies must be resolved
upon instantiation. These required dependencies are captured as LDAP query expressions
(see figure 3). LDAP query expressions are the standard way to request services in OSGi.
The query expressions are written in terms of the properties attached to the component
factory services, such as type name or version. An interesting feature of this approach is that
implementation resolution need not be precise; in other words, a component type can describe
an interface dependency in such a way to allow for multiple possible resolutions, such as
different versions of the same component type or different component types altogether.
5 Current Status and Future Work
Currently there is an implementation of the Beanome runtime that provides the following
features:
•
a generic activator that automatically interprets component descriptor files and
registers component factories,
•
component type instantiation and retrieval,
•
introspection facilities on component types and instances, and
•
access to the component type registry.
In addition to these features, several runtime extensions have been prototyped for dealing
with dependency conflict resolution, automatic deployment of bundles to resolve missing
dependencies, and shared instance persistence; these extensions to the runtime are
implemented as OSGi services.
In Beanome, the full spectrum of desirable service-to-service dependency types, as described
in subsection 3.1, is not completely supported. The current dependency semantics, which
occur between factory services, are one-to-one and static; this means there is only one source
and one target of the dependency, the dependency is required for instantiation, and the
dependency is not affected by changes in the environment. This definition of a dependency is
limited and forces complex dependency handling into the application.
In standard OSGi, an approach for removing complex dependency handling from application
code is to define the concept of a binding operation that is invokable when desired services
arrive so that it can be set externally. This binding operation is provided so that the bundle
classes or service implementations can receive references to the services they need. Using
such an approach, it is possible to describe application-level binding operations declaratively
in the bundle's meta-data so that binding can be processed externally.
Beanome introduces a new level of dependency not found in standard OSGi, since
component instances can use services, instance-to-service dependencies must also be
supported. To support these dependencies, the component descriptor format must be extended
to include cardinality and dynamism information. These changes to the component descriptor
format also require extending the Beanome runtime to properly handle optional dependencies
and to invoke appropriate binding operations at runtime.
A prototype of a service binder has been built to support the different types of dependencies
mentioned in subsection 3.1. This prototype provides automatic runtime binding support for
bundle-to-service and service-to-service dependencies, which are described in an XML file.
This prototype has not yet been fully integrated with the Beanome framework so instance-toservices dependencies are not yet covered.
Another important issue that requires further investigation concerns the departure of required
services or component types after a component has been instantiated. The current Beanome
prototype handles this situation by trying to revoke all component instances and their
associated interface implementation instances, but it cannot provide any guarantees that the
instances will not continue to be used since direct references to these object may be cached in
other objects. Perhaps a more consistent approach would be to shutdown the entire
dependency graph, but a more fine-grained solution is preferable.
6 Related work
Beanome is not creating a component model and framework for execution directly on
consumer devices, instead it is creating component concepts specifically for the OSGi
framework, which in turn focuses on the consumer device domain. Given this goal of
Beanome, it is difficult to compare it to other component models and framework for OSGi,
since no others are known to exist. Instead of direct comparisons, it is necessary to compare
Beanome to existing component models.
Beanome concepts are similar to those found in other component models. The concept of
component factories can be found in models such as EJB [7] and CCM [3]. Beanome
component types have a similar structure to CCM component types. The differences to CCM
are that CCM allows a component type to provide the same interface multiple times using
different names and that CCM includes the notion of event sources and sinks. The Beanome
introspection mechanisms leverage the standard JavaBeans [8] introspection technique for
type-level introspection, but Beanome extends the introspection capabilities to include
instance-level introspection. Both COM and CCM also allow type- and instance-level
introspection, similar to Beanome.
Another similarity to existing component models is that there is a separation between units of
delivery and components types. With respect to Beanome, the unit of delivery is the OSGi
bundle. For COM the unit of delivery is a dynamic linked library or the executable file itself.
EJB and CCM also have their own delivery units.
A related project is ROBOCOP [10] whose aim is to define “an open, component-based
partial architecture for the middleware layer in high volume embedded appliances that
enables robust and reliable operation, upgrading and extension, and component trading.”
ROBOCOP and Beanome share several concepts, which are inspired from COM ideas.
ROBOCOP's executable components are comparable to bundles and ROBOCOP's services
and service instances are comparable to Beanome's component types and component
instances. ROBOCOP, however, is not built on top of OSGi, nor does it address dynamic
dependencies.
Another related research project is PECOS [6], which aims to enable component-based
software development for embedded systems. It provides a component model that extends
industrial component models to address the needs of embedded systems and also provides an
“ultra-light” component environment where component-based applications are installed,
tested, and tuned. The PECOS component model has been defined to reflect an architectural
style built of components, ports, and connectors. PECOS is specifically defined to be used on
field devices, while Beanome is not, but it is possible to consider Beanome for such cases
when running the OSGi framework on limited resource Java virtual machines, like
PersonalJavaTM.
7 Conclusions
The OSGi framework provides a strong foundation on which to build service-oriented
applications that target the consumer device and residential gateway domains. The OSGi
framework benefits from the precise focus of its specification and the result is a framework
that is both conceptually and physically lightweight. A side-effect of this precise focus is that
complexity arises when building sophisticated applications on top of the framework because
OSGi is rather low-level.
Beanome, presented in this paper, adds a simple component model and framework on top of
the OSGi. The Beanome component model is implemented using standard OSGi mechanisms
and does not require any proprietary extensions. In Beanome, OSGi bundles become delivery
mechanism for component types, which can be used to instantiate complex application
graphs. The benefits of Beanome include: a hierarchical component model, an “application”
concept for deploying complex dependency graphs into the OSGi framework, and an
extensible component runtime using OSGi services.
Currently, a prototype exists that illustrates all of the concepts described in this paper, except
for instance-to-service dynamic dependencies. Future work will continue to concentrate on
dynamic dependencies, separation of the runtime into independent OSGi services, and
defining and improving services for extending the runtime, such as a persistence service and
a dependency conflict service. For further information on Beanome, visit http://wwwadele.imag.fr/BEANOME.
8 References
[1] Box, D., “Essential COM.” Addison Wesley, January 1998.
[2] Gamma, E., et al., “Design Patterns - Elements of Reusable Object-Oriented Software,”
Addison Wesley, 1995.
[3] Object Management Group, “CORBA Components: Joint Revised Submission,” August
1999.
[4] Open Services Gateway Initiative, http://www.osgi.org, 2002
[5] Open Services Gateway Initiative, “OSGI Service Platform,” Release 2, October 2001.
[6] Nierstrasz, O., et al., “A Component Model for Field Devices,” 1st Proceedings of the
International IFIP/ACM Working Conference on Component Deployment, June 2002.
[7] Sun Microsystems, “Enterprise JavaBeans Specification,” Version 2.0, 2001.
[8] Sun Microsystems, “JavaBeans Specification,” Version 1.01 edition, 1997.
[9] Bachman, Bass, Buhman, Comella-Dorda, Long, Robert, Seacord and Wallnau
“Volume II: Technical Concepts of Component Based Software Enginnering”, Technical
Report CMU/SEI-2000-TR-008, May 2000
[10] ITEA, ROBOCOP : Robust Open Component Based Software Architecture for
Configurable Devices Project – Framework concepts. Public Document V1.0, May 2002
Was this manual useful for you? yes no
Thank you for your participation!

* Your assessment is very important for improving the work of artificial intelligence, which forms the content of this project

Download PDF

advertisement