Thursday, April 23, 2009

OSGi Blueprint Service (RFC-124)

This post introduces the OSGi Blueprint Service (RFC-124) as it is currently drafted by the OSGi Enterprise Expert Group (EEG). I'll try to present the most relevant information in a fast and easy digestible way such that you should get a good idea of what Blueprint Service is about and why it is relevant for JBossOSGi.

Introduction

The OSGi platform provides an attractive foundation for building enterprise applications. However it lacks a rich component model for declaring components within a bundle and for instantiating, configuring, assembling and decorating such components when a bundle is started.

In 2006 SpringSource, the company behind the Spring Framework started development on a project called Spring Dynamic Modules to address the above needs. In Sep-2007 that project provided the basis for RFC-124 which is now known as "Blueprint Service".

Application Domain

The enterprise Java marketplace revolves around the Java Platform, Enterprise Edition (formerly known as J2EE) APIs. This includes APIs such as JMS, JPA, EJB, JTA, Java Servlets, JSF, JAXWS and others. The central component model of JEE is Enterprise JavaBeans (EJBs). Some core features of the enterprise programming models the market is moving to include:
  • A focus on writing business logic in “regular” Java classes that are not required to implement certain APIs or contracts in order to integrate with a container
  • Dependency injection: the ability for a component to be “given” its configuration values and references to any collaborators it needs without having to look them up. This keeps the component testable in isolation and reduces environment dependencies. Dependency injection is a special case of Inversion of Control.
  • Declarative specification of enterprise services. Transaction and security requirements for example are specified in metadata (typically XML or annotations) keeping the business logic free of such concerns. This also facilitates independent testing of components and reduces environment dependencies.
  • Aspects, or aspect-like functionality. The ability to specify in a single place behavior that augments the execution of one or more component operations.
In the JBoss Application Server all the core features from above are handled by the JBoss Microcontainer.

Problem Description

Enterprise application developers would like to be able to take advantage of the OSGi platform. The core features of enterprise programming models previously described must be retained for enterprise applications deployed in OSGi. The current OSGi specifications are lacking in the following areas with respect to this requirement:
  • There is no defined component model for the internal content of a bundle. Declarative Services only supports the declaration of components that are publicly exposed.
  • The configuration (property injection) and assembly (collaborator injection) support is very basic
  • There is no model for declarative specification of services that cut across several components (aspects or aspect-like functionality)
  • Components that interact with the OSGi runtime frequently need to depend on OSGi APIs
  • The set of types and resources visible from the context class loader is unspecified
  • Better tolerance of the dynamic aspects of the OSGi platform is required. The programming model should make it easy to deal with services that may come and go, and with collections of such services, via simple abstractions such as an injecting a constant reference to an object implementing a service interface, or to a managed collection of such objects.
Requirements (selected)
  • The solution MUST enable the instantiation and configuration of components inside a bundle based on metadata provided by the bundle developer.
  • The solution SHOULD enable the creation of components inside a bundle to be deferred until the dependencies of those components are satisfied.
  • The solution MUST provide a mechanism for a bundle component to be optionally exported as an OSGic service.
  • The solution MUST provide a mechanism for injecting a reference to an OSGi service into a bundle component. It SHOULD provide a constant service reference that the receiving component can use even if the target service backing the reference is changed at run time.
  • The solution SHOULD tolerate services in use being unregistered and support transparent rebinding to alternate services if so configured.
  • The solution SHOULD provide a rich set of instantiation, configuration, assembly, and decoration options for components
Solution

The runtime components to be created for a bundle, together with their configuration and assembly information, are specified declaratively in one or more configuration files contained within the bundle. A bundle with such information present is known as a managed bundle.

An extender bundle is responsible for observing the life cycle of such bundles. When a bundle is started, the extender creates a module context for that bundle from its blueprint by processing the configuration files and instantiating, configuring, and assembling the components specified there. When a managed bundle is stopped, the extender shuts down the module context, which causes the managed components within the context to be cleanly destroyed.

The declarative configuration for a bundle may also specify that certain of the bundle's managed components are to be exported as services in the OSGi service registry. In addition, it is possible to declare that a bundle component depends on a service or set of services obtained via the service registry, and to have those services dependency injected into the bundle component.



Module Context Life Cycle and the Extender Bundle

Module context creation and destruction

A Blueprint Service implementation must provide an extender bundle which manages the lifecycle of module contexts. This bundle is responsible for creating the module contexts for managed bundles (every ACTIVE managed bundle has one and only one associated module context).

When a managed bundle is stopped, the module context created for it is automatically destroyed. All services exported by the bundle will be unregistered


Lazy Activation


Module context instantiation is triggered when the STARTED event is issued for a given bundle, Therefore the module context for a lazily activated bundle will not be created until a class has been loaded from that bundle.

Manifest Headers for Managed Bundles

The extender recognizes a bundle as a managed bundle and will create an associated module context when the bundle is started if one or both of the following conditions is true:
  • The bundle path contains a folder OSGI-INF/blueprint with one or more files in that folder with a '.xml' extension.
  • META-INF/MANIFEST.MF contains a manifest header Bundle-Blueprint.
In the absence of the Bundle-Blueprint header the extender expects every ".xml" file in the OSGI-INF/blueprint folder to be a valid module context configuration file.

Two directives are defined by the blueprint service specification to control the manner in which module context creation occurs. These directives are applied to the Bundle-SymbolicName header.
  • blueprint.wait-for-dependencies (true|false) - controls whether or not module context creation should wait for any mandatory service dependencies to be satisfied before proceeding (the default), or proceed immediately without waiting if dependencies are not satisfied upon startup.
  • blueprint.timeout (300000) - the time to wait (in milliseconds) for mandatory dependencies to be satisfied before giving up and failing module context creation.
For example:

Bundle-SymbolicName: org.osgi.foobar;blueprint.wait-for-dependencies:=false

Bundle-SymbolicName: org.osgi.foobar;blueprint.timeout:=60000


Declaring Module Components

The basic structure of a configuration file is as follows:
<components xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0"...>
<description>
Optional description for the blueprint defined in this file.
</description>
<component id="..." class="...">
<!-- collaborators and configuration for this component go here -->
</component>
<component id="..." class="...">
<!-- collaborators and configuration for this component go here -->
</component>
<!-- more component definitions go here... -->
</components>
Naming Components

Every component has at most one id. Component ids must be unique within a module.

Implicit Component Definitions

A module context contains a number of implicitly defined components with well-known names.
  • ModuleContext - provides access to the component objects within the module context
  • Bundle and BundleContext - represents the bundle and its contextwith which the module context is associated
  • ConversionService - provides access to the type conversions that are defined for the module context
Instantiating Components

Object instantiation is supported in the following ways:
  • Using a constructor
  • Using a static factory method
  • Using an instance factory method
Dependencies

A typical module is made up of components that work (or collaborate) together. The basic principle behind Dependency Injection (DI) is that objects define their dependencies only through constructor arguments, arguments to a factory method, or properties which are set on the object instance after it has been constructed or returned from a factory method.

Dependency Injection is supported in the following ways:
  • Constructor Injection
  • Setter Injection
Type Conversion

String values in module context configuration files must be converted to the type expected by an injection target. The module context container supports a number of type conversions by default, and provides an extension mechanism for configuring additional type converters.

Lazily instantiated components

By default all singleton components in a module context will be pre-instantiated at startup. A lazily initialized component is not created at startup and will instead be created when it is first requested.

Component Scopes

Components can be defined to be deployed in one of a number of scopes, specified using the scope attribute:
  • Singleton - scopes a single component definition to a single object instance per module context
  • Prototype - scopes a single component definition to any number of object instances
  • Bundle - scopes a single component definition to a single object per requesting client bundle
Lifecycle

Specifying an init-method for a component enables a component to perform initialization work once all the necessary properties on a component have been set by the container. Specifying a destroy-method enables a component to get a callback when the module context containing it is destroyed.

Interacting with the Service Registry

The osgi namespace provides elements that can be used to export managed components as OSGi services, to define references to services obtained via the registry.

Exporting a managed component to the Service Registry

The service element is used to define a component representing an exported OSGi service. At a minimum you must specify the managed component to be exported, and the service interface that the service advertises.

For example

<service ref="comp" interface="com.xyz.MessageService"/>

Controlling the set of advertised service interfaces

The simplest mechanism, shown above, is to use the interface attribute to specify a fully-qualified interface name. To register a service under multiple interfaces the nested interfaces element can be used in place of the interface attribute.
<service ref="componentToBeExported">
<interfaces>
<value>com.xyz.MessageService</value>
<value>com.xyz.MarkerInterface</value>
</interfaces>
</service>
Using the auto-export attribute you can avoid the need to explicitly declare the service interfaces at all by analyzing the object class hierarchy and its interfaces. The auto-export attribute can have one of four values:
  • disabled : the default value; no auto-detected of service interfaces
  • interfaces : all of the Java interface types implemented by the component
  • class-hierarchy : component's implementation type and super-types
  • all-classes : component's implementation type and super-types plus all interfaces implemented by the component.
Controlling the set of advertised properties

Additional service properties can be specified using the nested service-properties element.
<service ref="componentToBeExported" interface="com.xyz.MyServiceInterface">
<service-properties>
<entry key="myOtherKey" value="aStringValue"/>
<entry key="aThirdKey" value-ref="componentToExposeAsProperty"/>
<service-properties>
</service>
The depends-on attribute

The depends-on attribute can be used to provide a comma-delimited list of component names for components that must be instantiated and configured before the service is published to the registry.

Dealing with service dynamics

The component defined by a reference element is unchanged throughout the lifetime of the module context (the object reference remains constant). However, the OSGi service that backs the reference may come and go at any time.

When the service backing a reference component goes away, an attempt is made to replace the backing service with another service matching the reference criteria. If no matching service is available, then the reference is said to be unsatisfied.

When an operation is invoked on an unsatisfied reference component, the invocation blocks until either the reference becomes satisfied or a timeout expires.

Mandatory dependencies

An exported service may depend, either directly or indirectly, on other services in order to perform its function. If one of these services is considered a mandatory dependency and the dependency can no longer be satisfied (because the backing service has gone away and there is no suitable replacement
available) then the exported service that depends on it will be automatically unregistered from the service registry.

Service Listeners

Applications that need to be aware of when a service backing a reference component is bound and unbound, or when a member is added to or removed from a collection, can register one or more listeners using the nested listener element.

For example:
<reference id="someService" interface="com.xyz.MessageService">
<listener bind-method="onBind" unbind-method="onUnbind">
<component class="MyCustomListener"/>
</listener>
</reference>
Namespace Extension Mechanism

Third parties may contribute additional namespaces containing elements and attributes used to configure the components for a module context.

In order to be able to interpret elements and attributes declared in third party namespaces, a namespace handler must be registered that the container can delegate to.

Here an example of a hypothetical "cache" namespace:
<cache:lru-cache id="myCache"/>
<component id="fooService" class="FooServiceImpl" cache:cache-return-values="true">
<cache:exclude>
<cache:operation name="getVolatile"/>
</cache:exclude>
<property name="myProp" value="12"/>
</component>
Credits

Adrian Colyer from SpringSource is the author of the Blueprint Service specification.

2 comments:

  1. The cleanest presentation I've read so far. Thank you for this clear highlight of the jey points of the RFC.

    ReplyDelete