Chapter 11. Component models and frameworks

 

This chapter covers

  • Understanding component-oriented concepts and terminology
  • Explaining how OSGi relates to component orientation
  • Exploring the OSGi Declarative Services component framework

 

So far in this book, we’ve shown you how to develop applications using the core OSGi framework layers: module, lifecycle, and service. In chapter 2, we mentioned the similarities between module- and component-oriented programming. In chapter 4, we mentioned how the OSGi service model can work alongside component models. There’s obviously some degree of synergy between OSGi and component technologies. This has led a variety of existing component technologies to integrate with OSGi as well as a variety of new component frameworks being built on top of it.

Component-oriented approaches have become incredibly popular in Java development over the past decade, and a vast number of approaches are available, including Enterprise JavaBeans (EJB), Spring Beans, Google Guice, Service Component Architecture (SCA), and Fractal, to name just a few. The variety and variation among component-oriented approaches is staggering, but one thing is typically common: they ignore or only pay lip service to modularity issues related to deployment and execution-time verification and enforcement. This means OSGi technology provides a perfect foundation for integrating existing component approaches or defining new ones.

In this chapter and the next, we’ll introduce you to component orientation in general and as it relates to OSGi technology. This chapter will cover introductory aspects and present the first OSGi standard component framework, called Declarative Services, which is lightweight and fairly representative of how component frameworks are integrated with OSGi. In the next chapter, we’ll introduce a couple more advanced component frameworks. We’ll reuse the example paint program to illustrate how these component frameworks simplify OSGi-based development. Let’s start with background information and motivation.

11.1. Understanding component orientation

Although component-oriented programming has been around for a while, there’s no single definition for most of the concepts it embodies (which is similar to module orientation). Therefore, you shouldn’t take the discussion in this section as the bible for all component-oriented approaches. The main questions we intend to address for the scope of this chapter and the next are, what are components, and why do we want them? We’ll answer these questions in the following two subsections, respectively.

11.1.1. What are components?

A key aspect of all component technologies is that they describe functional building blocks that are typically more coarse-grained than what we normally associate with objects (although object orientation isn’t required for component orientation). These building blocks are typically business logic; they provide functionality via interfaces. Conversely, components may consume functionality provided by other components via their interfaces. Components for a given approach are usually programmed according to a particular pattern defined by a component model. A component framework is used to execute components.

 

Component model vs. component framework

A component model describes what a component looks like, how it interacts with other components, and what capabilities it has (such as lifecycle or configuration management). A component framework implements the runtime needed to support a component model and execute the components. The relationship between the two isn’t strictly one-to-one. For example, the Common Object Model (COM) defines a component model that’s implemented by different component frameworks for different platforms. Likewise, it’s also possible for a component framework to support multiple component models, such as the JBoss Microcontainer.

Component frameworks aren’t constrained by the component model they support and may provide additional capabilities. This is common when vendors try to differentiate implementations of standard component models; think about how Java EE application servers try to differentiate themselves. The reality is that no clear line separates a component model from a component framework. The important differentiation to take away is that a component model describes what it means to be a component, and the framework provides the runtime to execute components adhering to a component model.

 

Generally speaking, components have some explicit way of declaring their provided interfaces. This can be done through certain patterns, such as implementing an interface or extending a base class, or it can be done more explicitly at execution time by publishing provided interfaces, such as using an interface repository. Likewise, components may have some explicit way of declaring their dependencies on the provided interfaces of other components, such as with declarative metadata, or they may be responsible for managing their own dependencies at execution time, such as querying an interface repository. Often, components are packaged as independent deployment units, such as JAR files or DLLs, but this isn’t strictly necessary.

 

Modules vs. components

Doesn’t it sound like modules and components have a lot in common? They both provide stuff to each other and consume stuff from each other. They’re also packaged as independent deployment units. Couldn’t these two be considered one and the same or at least be combined? Yes, they could, but components and modules serve different purposes and are somewhat orthogonal (they’re not completely orthogonal, because components are made from code that can ultimately be packaged into modules).

Modules deal with code packaging and the dependencies among code. Components deal with implementing higher-level functionality and the dependencies among components. Components need their code dependencies managed, but they technically don’t need a module system to do it (often it’s us programmers doing it via the class path).

A good summary is that you can think of modules as dealing with static code and compile-time dependencies, whereas components deal with instances and execution-time dependencies.

 

The general approach for creating an application from components is to compose it. This means you grab the components implementing the functionality you need and compose them (match required interfaces to provided interfaces) to form an application. Component compositions can be declarative, such as using some sort of composition language to describe the components and bindings among them; or implicit, where the composition is the deployed set of components. For the application to execute, the application’s constituent components must somehow be loaded into the component framework and instantiated. Figure 11.1 shows a trivial component composition.

Figure 11.1. Trivial component composition of two components: FooImpl and BarImpl

This description of component orientation is by no means complete. Depending on the component model, components may have a variety of capabilities, such as explicit lifecycle control. Some component models and frameworks differentiate between component types and instances (for example, there can be multiple component instances from a given type), whereas others treat them as being the same (only one instance per component). You’ll see some of these differences rear their heads in our later discussions of specific OSGi-based component frameworks. For now, it’s sufficient if your general understanding of component orientation is as a programming approach promoting coarse-grained, composable application building blocks. Now let’s look at why we want it.

11.1.2. Why do we want components?

The long-held promise of component orientation is that we’ll be able to create applications easily and quickly by snapping them together from readily available, reusable components. The actual merits of this rosy view of component orientation are debatable, but there are benefits to be gained by adhering to a component model. First and foremost, it promotes separation of concerns and encapsulation with its interface-based approach. This enhances the reusability of your code because it limits dependencies on implementation details.

Another worthwhile aspect of an interface-based approach is substitutability of providers. Because component interaction occurs through well-defined interfaces, the semantics of these interfaces must themselves be well defined. As such, it’s possible to create different implementations and easily substitute one provider with another. You may have noticed that these benefits are pretty much the same as we described for OSGi services. This is part of the reason why such a strong synergy exists between OSGi and component technologies; more on this shortly.

Because component models typically make the provided and required interfaces of components explicit (or at least explicitly focus on them), you end up with more reusable software that’s amenable to composition. And because component models typically require a specific pattern or approach for development, your code ends up more uniform and easier to understand. This uniformity also leads to another potential benefit: external management.

This last point isn’t necessarily obvious; but by creating components following a specific model, external entities can understand more about your code and potentially take some tasks off your hands. For example, transactions and persistence are handled automatically for components in EJB. Another example is distribution, where some component frameworks automatically make components remotely accessible. And as you’ll see in this chapter, execution-time dependency management is also possible.

This all sounds useful, but are there any downsides to using components? Yes, there are always issues to be considered in any architectural decision. Table 11.1 details some general issues you should consider when choosing whether to use components.

Table 11.1. Potential issues associated with component orientation

Problem

Description

Analysis

Bloat Some component frameworks are relatively heavy, so they may not be appropriate for small applications. How complex are your application dependencies? Is the extra functionality provided by a component framework required?
Diagnosis Debugging service-dependency problems requires a new set of tools to figure out what’s going on when your services aren’t published as expected. Debugging dependency problems is often simplified by having generic tools that can be applied to common component models.
Side-file syndrome Build- or execution-time problems caused by component configuration files becoming stale with respect to Java source code can be frustrating to debug. IDE tooling can definitely help by providing refactoring support and early analysis. A number of projects are building in support of component models, and they will only increase over time.

Overall, we feel the positives far outweigh the potential negatives. Given this general motivation for component orientation, let’s move on to discussing how all this relates specifically to OSGi.

11.2. OSGi and components

For those reading between the lines in the last section, it may not come as a complete surprise, but there’s a reason why the synergy between OSGi and component technologies is so strong. The core OSGi specification defines a component model and the framework for executing the corresponding components. Yes, that’s right: OSGi developers are component developers. The type of component model defined by OSGi is a special kind, called a service-oriented component model. Let’s take a minute to look at the specification in this new light.

11.2.1. OSGi’s service-oriented component model

The high-level description of the OSGi component model can be understood by equating bundles with components and services with component interfaces. We’ll put a little more meat on this description by breaking down how the OSGi core specification maps to the component-oriented concepts of the last section.

Components and their Interaction

For a bundle to be a component, it implements a bundle activator. The activator also allows for lifecycle management. Another capability includes external configuration management using BundleContext.getProperty(), although the exact details are left to the framework implementation.

The bundle JAR file is the independent unit of deployment for a component. In OSGi, the logical bundle (the component) is equated with the physical bundle JAR file (the module). Technically, this means there can be only one component per module and, further, only one component instance. Later, you’ll see that most OSGi-based component frameworks break this one-to-one physical-to-logical mapping, but this mapping isn’t the important part of the OSGi component model. OSGi’s killer feature is in the richness of its dynamic module layer. Some component models and frameworks deal with modularity (the code level), but few if any provide such rich features.

At the module level, as you’ve learned, bundles have a way of explicitly describing the code they provide and require. At the component level, the core OSGi specification doesn’t define a way for bundles to explicitly declare the services they provide and require. Instead, these issues are left to be handled manually by the bundle at execution time.

Component Framework

The distinction between component model and framework is definitely blurred in the OSGi specification, because it goes to great lengths to ensure that the component framework has standardized behavior. This ultimately makes aspects of what might ordinarily be thought of as belonging to the component framework part of the component model.

The OSGi-based component frameworks described here and in the following chapter can be seen as extensions to the OSGi component model and framework. This is sometimes confusing because the distinction of what is part of the model, the framework, or components themselves isn’t always obvious. For example, the OSGi Configuration Admin Service defines how bundles can be configured. But it isn’t part of the OSGi component model, nor the OSGi component framework; it’s an agreement among the Configuration Admin component and its client components.

But this is how it should be. Keep the model and framework simple and small. Try to do everything else in the layers above. The framework should be the execution environment for components and little else.

Component Composition

Just as there isn’t an explicit way for bundles to declare their provided and required services, the core OSGi specification doesn’t define an explicit way to compose an application. In OSGi, the composition of a component-based application is the set of deployed bundles. The interesting part is how the composition is constructed (matching provided services to required services), which is done at execution time via the service registry. This service-oriented interaction pattern among components is what makes the OSGi approach a service-oriented component model.

Using execution-time service binding means dependencies are resolved late. Further, the use of an interfaced-based interaction via services enables substitutability of providers. Combining late binding and provider substitutability results in a flexible component model where compositions are malleable, because they don’t specify explicit component implementations, nor precise bindings among them. In the OSGi model, this also opens up the possibility of advanced scenarios based on execution-time dynamism.

 

Service-oriented component models

Service-oriented component models rely on execution-time binding of provided services to required services using the service-oriented interaction pattern (publish-find-bind). Often, execution-time dynamism is also associated with service-oriented component models, but this isn’t technically a requirement to receive some of the benefits. For example, COM follows a similar approach of execution-time binding to required components, but it doesn’t assume that these components will also come and go during application execution. Still, following this approach allows you to treat the deployed set of components as the application configuration, which leads to flexibility in your application composition.

 

The OSGi approach is flexible, but it’s also a little low-level. For example, although OSGi uses an API-based approach, many modern component models use or are moving toward an API-less approach, such as using Plain Old Java Objects (POJOs) as components. This has led to the creation of several OSGi-based component frameworks and/or extensions to the core OSGi approach. In the next section, we’ll provide an overview of what these additional component frameworks are trying to achieve.

11.2.2. Improving upon OSGi’s component model

The main weakness of the OSGi component model is its reliance on components manually managing their own service-level dependencies, even though module-level dependencies are automatically managed. Some of the earliest work on improving the OSGi component model was done to address this complexity, such as by Beanome (www.humbertocervantes.net/papers/ISADS2002.pdf) and Service Binder (www.humbertocervantes.net/papers/ESEC2003.pdf). All of the component frameworks we’ll discuss in this chapter and the next also address this issue. Because the approaches have a lot of similarities, we’ll try to describe some of the issues in a general way here.

General Approach

OSGi-based component frameworks adopt the bundle JAR file as the deployment unit for components. In general, they break the “one component per JAR file” approach of the standard OSGi component framework and allow any number of components to be contained in it. They all define additional, component-related metadata, which is packaged in the bundle to describe the components contained in the bundle JAR file. They then employ the extender pattern to listen for bundles containing components to come and go so they can start managing the contained components, as shown in figure 11.2. A component’s description defines which services it provides and which it requires; we’ll go into a little more depth on this topic shortly.

Figure 11.2. Component frameworks in OSGi are generally implemented as other bundles using the extender pattern. They remove boilerplate code from user bundles (often using metadata files—OSGI-INF/foo.xml in this case), allowing the deployer to build complex business services out of modular building blocks.

The lifecycles of components contained in a bundle are subservient to their containing bundle, meaning that components can only be active if their containing bundle is active. Beyond that, the lifecycle of an individual component is based on its own dependencies and constraints. Component frameworks typically define valid and invalid component lifecycle states based on whether a component’s dependencies are satisfied. Because the lifecycle of the contained components is managed by the component framework, component bundles typically don’t have bundle activators. A component framework listens for bundle lifecycle state changes in the component bundles to trigger management. As a result, component frameworks introduce some other sort of callback mechanism (a lifecycle hook) for components wishing for such notification; you can think of this as a component activator. Luckily, such lifecycle hooks are usually unnecessary, because services and service dependencies are managed automatically, which was the main purpose for having a bundle activator in the first place.

Automating Service Management

The most immediate benefit of having the component framework manage service dependencies is the simplification it brings. It removes redundant boilerplate code for handling each service dependency, and it also eliminates some of the complex, error-prone aspects.

Consider a trivial example where a component FooImpl depends on service Bar and should only publish its service when Bar is available. This is the scenario shown in figure 11.1; the following listing shows the code necessary to achieve it using a ServiceTracker (refer to chapter 4 for a reminder of how ServiceTracker works).

Listing 11.1. ServiceTracker handling one-to-one dependency on a service

BarTracker tracks Bar services. If one is discovered, it checks whether this is the first Bar service it has found . If so, it calls the FooImpl.setBar() method prior to registering the Foo service of FooImpl. If more than one Bar service is found, backups are stored . If BarTracker detects that the Bar service being used has been removed , it replaces that service with one of the backups . If no backup is available, it unregisters the Foo service and calls the FooImpl.setBar() method with null.

You may be looking at this code and thinking that it looks complicated. We agree that it’s reasonably so, particularly if you also consider that it covers only a single, one-to-one service dependency. Things get more complex (and redundant) as you get more dependencies. OSGi-based component frameworks allow you to describe these types of issues; then the frameworks worry about it for us. Typically, the component frameworks let you describe the following:

  • Provided services— Services implemented by the component
  • Required services— Services needed by the component in order to provide its services

If the component’s required services are satisfied, the component framework can instantiate the component and publish its provided services into the service registry. The descriptions of provided services are normally straightforward (just mentioning the interfaces under which to publish the component), but the descriptions of required services can be rich. A service’s dependency description may include the following:

  • Service type— Actual type of required service
  • Optionality— Whether the dependency is mandatory or the component can function without it
  • Cardinality— Whether the dependency is for a single service instance (one-to-one) or for an aggregate number of service instances (one-to-many)
  • Lifecycle impact— Whether execution-time changes are visible to the component (dynamic) or invalidate the entire component instance (static)

Most of these characteristics are reasonably self-explanatory. The last one, lifecycle impact, is a little trickier. Because dynamism adds complexity, some component frameworks allow components to control how much service dynamism a component sees. If a component wants to treat a given dependency as having a static lifecycle, it won’t see new services arriving after instantiation and will be completely invalidated if a service being used goes away. On the other hand, dependencies having a dynamic lifecycle can potentially see (and handle) service dynamism at execution time without being invalidated. For example, you may want to create a component with an optional, dynamic dependency on a log service. If the log service isn’t there, your component can function without it; but if one arrives, your component can start using it as soon as it’s available.

Don’t worry if these service dependency characteristics are a little fuzzy at this point; they’ll become clearer as we look into the various component frameworks in more detail.

Simplifying Other Management Areas

Another area where component frameworks can help in removal of boilerplate code is in the management of component configuration. In chapter 9, you saw how you can use the Configuration Admin Service to configure OSGi services using a simple portable model. Still, the developer must provide some boilerplate code to interact with this service. Most component frameworks provide ways to simplify component configuration and interaction with the Configuration Admin Service.

Finally, a number of component frameworks allow for custom extension points to allow third-party providers to provide advanced capabilities such as audit management, persistent state, and transaction management using declarative hooks (either in user code or via side files). These sorts of capabilities turn component frameworks into rich programming environments, allowing you to strip away the layers and focus your code on the core of your business process without sacrificing portability.

Having introduced component models and how they relate to modularity in general and OSGi specifically, let’s now turn our attention to a practical demonstration of using component models and frameworks in OSGi.

11.2.3. Painting with components

In this chapter and the next, we’ll look at three different OSGi-based component frameworks: Declarative Services, Blueprint Container, and iPOJO. You’ll re-create the example paint program using each of these component frameworks. For each, you’ll have the components shown in figure 11.3, where each component is packaged in its own bundle (although this isn’t strictly necessary).

Figure 11.3. Components used in the modified paint application

The PaintFrame component provides a Window service and has an aggregate dependency on SimpleShape services. The shape components each export a single Simple-Shape service, with no service dependencies. The WindowListener component has a mandatory, one-to-one dependency on a Window service; it shuts down the framework when the window it’s bound to closes. The WindowListener also has an optional, one-to-one dependency on a LogService to log a message when the window is closed.

All versions of the paint program function like the original, but how they achieve this varies quite a bit. Let’s take this high-level of view of OSGi-based component frameworks and make it concrete by turning our attention to the first component framework on the list: Declarative Services.

11.3. Declarative Services

The Declarative Services specification was defined by the OSGi Alliance as part of the R4 compendium specification. It defines a lightweight approach for managing service dependencies. The focus of the Declarative Services specification is to address three main areas of concern, outlined in table 11.2.

Table 11.2. The Declarative Services’ raison d’être

Area of concern

Discussion

Startup time Because many bundles have bundle activators, the initialization time of each bundle adds to the initialization time of the entire application.
Memory footprint Registering services often implies the creation of many classes and objects up front to support the services. These classes and objects needlessly consume memory and resources even if these services are never used.
Complexity A large amount of boilerplate code is required to handle complex service-dependency scenarios. Management of this code in small scenarios is at a minimum a chore, but in large environments this boilerplate code represents a real risk in terms of software maintenance.

The Declarative Services specification addresses these issues by managing service publication and dependencies for components. Managing service publication on behalf of components allows Declarative Services to defer service creation and improve both startup performance and the memory footprint; and managing service dependencies reduces complexity. We’ll look into precisely how Declarative Services does these things in the remainder of this chapter.

11.3.1. Building Declarative Services components

Let’s start by example. Consider the circle bundle converted to use Declarative Services (the square and triangle bundles follow the same pattern). If you inspect the contents of the new circle bundle in the chapter11/paint-example-ds/ directory of the companion code, you’ll see that it has the following contents:

META-INF/MANIFEST.MF
OSGI-INF/
OSGI-INF/circle.xml
org/
org/foo/
org/foo/shape/
org/foo/shape/circle/
org/foo/shape/circle/Circle.class
org/foo/shape/circle/circle.png

If you remember the previous version, the first thing you’ll notice is that it no longer contains a BundleActivator implementation. Interesting. If the bundle doesn’t have an activator, how does it provide and use services? The clue you need is located in the bundle’s manifest file, which has the following new entry:

Service-Component: OSGI-INF/circle.xml

This header is defined by the Declarative Services specification. It serves two purposes: its existence tells the Declarative Services framework that this bundle contains components and the referenced XML file contains metadata describing the contained components. When a bundle is installed into the OSGi framework, the Declarative Services framework follows the extender pattern and probes for this manifest entry. If it exists, the Declarative Services framework takes over management of the contained components according to the information in the referenced file.

In Declarative Services, the convention is to place component description files in the bundle’s OSGI-INF/ directory following an OSGI-INF/<component-name>.xml naming convention, but the files can go anywhere within the bundle. It’s possible to include multiple component files in a single bundle using a comma-delimited set, a * wildcard pattern, or a mixture of the two.

 

Fragmented components

It’s also possible to place component descriptions into bundle fragments (which we covered in chapter 5). In this scenario, only the host bundle’s Service-Component manifest header is read, although the XML files may reside in the bundle fragments. A possible use case for doing this is if you want to support several different component configuration options where you choose at deployment time which is instantiated. We don’t classify this as a recommended use case, because fragments bring in all sorts of complex lifecycle issues and break a number of best practices with respect to modular boundaries, but we cover it here for the sake of completeness.

 

This explains the lack of a bundle activator for your component bundle, but how exactly does your SimpleShape service get published? Next, we’ll look more closely at the component description file to see how components declare their provided services.

11.3.2. Providing services with Declarative Services

The following code snippet shows the declaration used to tell the Declarative Services framework to publish your Circle class as a service in the OSGi service registry under the SimpleShape interface:

<?xml version="1.0" encoding="UTF-8"?>
<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0">
  <property name="simple.shape.name" value="Circle" />
  <property name="simple.shape.icon" value="circle.png" />
  <scr:implementation class="org.foo.shape.circle.Circle" />
  <scr:service>
     <scr:provide interface="org.foo.shape.SimpleShape"/>
  </scr:service>
</scr:component>

In this metadata, you define two properties for your component. Properties may be used to configure a component (which we’ll look at shortly), but they also act as service properties and are automatically attached to services published by a component. In this case, these properties are used by your paint frame to identify the shape. You define the component’s implementation class, which must be reachable on the bundle class path where this component description is located. Finally, you declare that the component provides the SimpleShape service. Let’s now look at the Circle class.

Listing 11.2. Circle class used in the Declarative Services paint example
public class Circle implements SimpleShape {
  public void draw(Graphics2D g2, Point p) {
    int x = p.x - 25;
    int y = p.y - 25;

    GradientPaint gradient =
      new GradientPaint(x, y, Color.RED, x + 50, y, Color.WHITE);
    g2.setPaint(gradient);
    g2.fill(new Ellipse2D.Double(x, y, 50, 50));
    BasicStroke wideStroke = new BasicStroke(2.0f);
    g2.setColor(Color.black);
    g2.setStroke(wideStroke);
    g2.draw(new Ellipse2D.Double(x, y, 50, 50));
  }
}

This Circle class is exactly the same as the prior version. All you do is drop the associated bundle activator and add the component description instead. Before moving on to consuming services, let’s discuss component properties a little further.

Component Properties

For the circle component, you use the component properties to specify service properties. This follows a pattern of property propagation similar to what you saw in chapter 9 for Configuration Admin, where configuration properties are attached to the service. In the paint example, you statically declare the properties in a component XML file using the <property> element. This is the last place a Declarative Services framework looks for component properties. Component properties may come from

  1. Properties passed to the ComponentFactory.newInstance() method (we’ll cover component factories in section 11.3.4)
  2. Properties retrieved from the Configuration Admin service using the component name as the PID
  3. Properties defined in the component description file

The priority of these properties is as listed, with the first having the highest priority. This means properties coming from a higher-priority source override the same property coming from a lower-priority source. This precedence behavior allows a developer to assign default component properties, but another user or system administrator can change the configuration at execution time to suit their specific needs.

 

Declarative Services have simple attribute types

One limitation arises from using Declarative Services as opposed to the core OSGi framework API: Declarative Services components can only use service properties with simple Java types (String [default], Long, Double, Float, Integer, Byte, Character, Boolean, and Short). In the previous version of the paint program, you added the shape icon object directly as a service property using the following code:

dict.put(SimpleShape.ICON_PROPERTY,
  new ImageIcon(this.getClass().getResource("circle.png")));

In the vast majority of situations, this limitation isn’t a big deal, but it’s something to consider. We’ll return to this topic briefly in the next section and in the following chapter when we discuss the Blueprint and iPOJO component frameworks.

 

Providing services is straightforward: a component declares which internal class implements which provided service interface, and the Declarative Services framework publishes the service at execution time after the bundle containing the component descriptions is activated. What happens if the component has dependencies on other services?

11.3.3. Consuming services with Declarative Services

To see how Declarative Services components can use services, let’s turn our attention to the paint program’s paint frame. Again, you modify the bundle to contain a component description in OSGI-INF/paint.xml, as shown next.

Listing 11.3. Declarative Services’ description of PaintFrame component

There’s quite a bit of information in this component description. Looking at some of the aspects that are similar to the circle component, you see the component declaration, but this time you assign paint as its name. You specify the component implementation class, which you indicate as providing a java.awt.Window service with a service property, called name. You introduce a new element , defining a “reference” (a dependency) to a service implementing the SimpleShape interface.

If you’re familiar with other dependency-injection frameworks, such as Spring, this should be starting to feel familiar. You specify the method PaintFrame.addShape() to use for injecting discovered SimpleShape services. Also, because you’re in an OSGi environment where services can come and go, you specify the method Paint-Frame.removeShape() to use when a shape service goes away. There are other service-dependency characteristics, but before we go into the details of those, let’s talk more about binding methods.

Binding Method Signatures

The Declarative Services specification defines the following method signatures for binding methods:


  • void <method-name>(ServiceReference);

  • void <method-name>(<parameter-type>);

  • void <method-name>(<parameter-type>, Map);

The first form injects the service’s associated ServiceReference into the component instead of the service object itself. This allows the component to find out which services are available in the framework without retrieving them. This method is typically used in conjunction with the ComponentContext, which we’ll discuss a little later, to implement extremely lightweight solutions where service objects are created only when absolutely necessary.

The second form should look familiar to most programmers who have used some form of dependency-injection framework. Using this binding method, the Declarative Services implementation retrieves the actual service object from the OSGi service registry and injects it into the component. The component developer may choose to store a reference to the service object; but you must take care to dereference the service when the corresponding unbind method is called, to prevent memory leakage.

The third form behaves much like the second, except that the associated service properties are also injected into the component. Because you need the service properties to retrieve the shape name and icon for the paint frame component, this is the form you’ll use. The PaintFrame.addShape() method is as follows:

void addShape(SimpleShape shape, Map attrs) {
  final DefaultShape delegate = new DefaultShape(shape);
  final String name = (String) attrs.get(SimpleShape.NAME_PROPERTY);
  final Icon icon = new ImageIcon(shape.getClass().getResource(
    (String) attrs.get(SimpleShape.ICON_PROPERTY)));

  m_shapes.put(name, delegate);
  ...
}

The Declarative Services framework calls this addShape() method when any Simple-Shape service is published in the OSGi service registry, passing in the service and the associated map of service properties. You read the name property of the shape and load its ImageIcon representation. As we mentioned earlier, the Declarative Services specification is only able to handle simple property types, so in this version of the paint frame component you have to explicitly load the resource via the shape object’s class loader. Finally, you store a reference to the shape service object in an internal map for use later.

Conversely, when a shape service is removed from the service registry, the Declarative Services framework invokes the PaintFrame.removeShape() method:

void removeShape(SimpleShape shape, Map attrs) {
  final String name = (String) attrs.get(SimpleShape.NAME_PROPERTY);

  DefaultShape delegate = (DefaultShape) m_shapes.remove(name);

  ...
}

You use the binding method form that supplies the service attributes as a map. You use the name property from the map to figure out which component has been removed and remove your reference to the service object from the internal map.

 

Binding method accessibility

You may have noticed that the binding methods you’ve defined have package-private visibility. The Declarative Services specification states the following with regard to method visibility:

  • public—Access permitted
  • protected—Access permitted
  • package private—Access permitted if the method is declared in an implementation class or any superclass within the same package
  • private—Access permitted if the method is declared in an implementation class

As a matter of best practice, you should generally protect binding methods, because doing so prevents external code from injecting services out of band of the main service-registry lifecycle (assuming the Java security manager is enabled—we’ll look at security in chapter 14).

 

Now, let’s return our attention to how components describe their service dependencies. So far, we’ve discussed that the component service dependency description includes a service interface and binding methods. Other, more sophisticated dependency characteristics are available.

Sophisticated Service Dependency Characteristics

Many service dependencies fall into the category of being a hard dependency on a single service object. But what if your component is different? Recall the following snippet from listing 11.3:

  <scr:reference
    interface="org.foo.shape.SimpleShape"
    cardinality="0..n"
    policy="dynamic"
    bind="addShape"
    unbind="removeShape"/>

We haven’t discussed the cardinality and policy dependency characteristics yet, but they help you address more sophisticated service dependency situations. The notion of cardinality plays two roles in the Declarative Services specification:

  • Optionality— Cardinality values starting with 0 are treated as optional, whereas values starting with 1 are treated as mandatory.
  • Aggregation— Cardinality values ending with 1 are treated as a dependency on a single service object of the specified type, whereas values ending in n are treated as a dependency on all available service objects of the specified type.

The possible cardinality values defined by the Declarative Services specification are 0..1 (optional, singular dependency), 1..1 (mandatory, singular dependency), 0..n (optional, aggregate dependency), and 1..n (mandatory, aggregate dependency). From the snippet, you can see that the paint frame has an optional, aggregate dependency on shape services; this means it wants to be bound to all available shape services, but doesn’t need any to function. Cardinality is fairly straightforward, but the dependency policy is a little trickier to understand.

A component service dependency can be declared with either of two policy values: dynamic or static. What does this mean? A dynamic policy means that the component is notified whenever the service comes or goes, whereas with a static policy, the service is injected once and not changed until the component is deactivated. In essence, if you use a dynamic policy, your component needs to cope with the possible issues (such as threading and synchronization) resulting from service dynamism. If you use a static policy, you don’t need to worry about issues related to service dynamism, but your component sees only one view of the services published in the OSGi registry while it’s active.

This dependency policy also relates to component lifecycle management. For example, the paint frame component specifies a dynamic policy. Therefore, if a shape it’s using goes away, it sees the change immediately and dynamically adapts accordingly. You’ve seen this in earlier examples, where you dynamically added and removed shapes. If this dependency were specified as static, then if a shape service being used by the paint frame departed, the paint frame component instance would need to be thrown away, because a static policy means the component isn’t programmed such that it can handle service dynamism. We’ll continue this discussion about component lifecycle in the next subsection.

Another characteristic of service dependencies is a target filter. To illustrate, let’s look at the WindowListener component of the modified paint program; its Declarative Services component description is as follows.

Listing 11.4. Metadata for WindowListener with optional LogService dependency

The WindowListener component has a static, singular, and mandatory dependency on a Window service . You specify a target LDAP filter to locate the specific Window service of interest ; recall that the filter references the property you associated with the PaintFrame component’s Window service in its component description in listing 11.3. Additionally, the WindowListener component has a dynamic, singular, and optional dependency on a log service.

 

Target reference properties

You saw earlier that component properties can be used to define the service properties associated with a component’s provided service. Component properties can also be used to configure service-dependency target filters at execution time. To do this, the property name must be equal to the name associated with the service reference appended with .target. In this case, you could override the window target using a property of this form:

<property name="window.target" value="(name=other)" />

This binds the window listener to windows attributed with the name=other identifier. Doing this directly in the static component description is of relatively low value. But if you remember the discussion on component properties, these values can also be set at execution time via the Configuration Admin Service or using component factories, which opens up a set of interesting use cases.

 

Filtering services based on attributes is relatively easy and, at first glance, dealing with optional services appears equally easy. But there are some subtle mechanics of which you need to be aware when using the dynamic dependency policy. Here are the relevant lines from the WindowListener component.

Listing 11.5. WindowListener with optional LogService dependency
public class WindowListener extends WindowAdapter {
...
  private AtomicReference<LogService> logRef =
    new AtomicReference<LogService>();

  protected void bindLog(LogService log) {
    logRef.compareAndSet(null, log);
  }

  protected void unbindLog(LogService log) {
    logRef.compareAndSet(log, null);
  }
...
  private void log(int level, String msg) {
    LogService log = logRef.get();

  if (log != null) {
    log.log(level,msg);
  }
 }
}

Here, you use a java.util.concurrent.AtomicReference object to hold the Log-Service, which you set in the binding methods. You use an AtomicReference to protect yourself from threading issues related to the service being bound or unbound while your component is using it. You also need to be aware of the fact that the Log-Service may in fact not be bound because it’s optional, so you check whether the service is bound and log a message if so. The use of a wrapper method to achieve this is one possible mechanism; for a more advanced solution, you could use null objects to protect other areas of code from this execution-time issue.

So far, you’ve seen how to describe components that publish and consume services, but we’ve only indirectly discussed component lifecycle management. Next, we’ll provide more details about the lifecycle of Declarative Services components.

11.3.4. Declarative Services component lifecycle

Having described your components, the next issue to consider is their lifecycle. When are components created? When are they destroyed? Are there any callbacks at these stages? How can you access the BundleContext if there is no BundleActivator? We’ll deal with each of these questions in this section.

Component Lifecycle Stages

In chapter 3, we introduced the bundle lifecycle: in essence, bundles are installed, then resolved, and then activated. Declarative Services defines a similar lifecycle for components, where they’re enabled, then satisfied, and then activated. The Declarative Services specification defines the following stages to a component lifecycle:

  • Enabled— A simple Boolean flag controls whether the component is eligible for management.
  • Satisfied— The component is enabled, its mandatory dependencies are satisfied, any provided services are published in the service registry, but the component itself isn’t yet instantiated.
  • Activated— The component is enabled, its mandatory dependencies are satisfied, any provided services are published in the service registry, and the component instance has been created as a result of a request to use its service.
  • Modified— The configuration associated with the component has changed, and the component instance should be notified.
  • Deactivated— Either the component has been disabled or its mandatory dependencies are no longer satisfied, so its provided services are no longer available and its component instance, if created, is dereferenced for garbage collection.

You can enable/disable a component declaratively using the enabled attribute of the <component> XML element and programmatically using the ComponentContext interface, which you’ll see shortly. A simple use case for this is to reduce startup time by disabling all but a small number of components and then enabling additional components later as needed. Similarly, neither the enabled nor satisfied stages result in instantiating the component class in an effort to avoid unnecessary work.

When a component is enabled, it may become satisfied. A component can become satisfied only if all of its mandatory dependencies are satisfied. After it’s satisfied, it may become activated if its provided service is requested. Activation results in the component being instantiated. Each component description ultimately is reified as a single component instance that will be managed by the Declarative Services framework; by default, a one-to-one mapping exists between a component description and a component instance.

The component lifecycle is coupled to the lifecycle of its containing bundle. Only components in activated bundles are eligible for lifecycle management. If a bundle is stopped, the Declarative Services framework automatically deactivates all activated components contained in it.

Let’s dive into some code to see what this means in practice for the paint application. First, let’s look at the PaintFrame class.

Listing 11.6. Lifecycle-related code from Declarative Services PaintFrame class
public PaintFrame() {
  super("PaintFrame");
...
}
...
void activate(Map properties) {
  Integer w = (Integer) properties.get(".width");
  Integer h = (Integer) properties.get(".height");

  int width = w == null ? 400 : w;
  int height = h == null ? 400 : h;

  setSize(width, height);

  SwingUtils.invokeAndWait(new Runnable() {
    public void run() {
      setVisible(true);
    }
  });
}

void deactivate() {
  SwingUtils.invokeLater(new Runnable() {
    public void run() {
      setVisible(false);
      dispose();
    }
  });
}
...
}

You define a default, no-argument constructor for the component class; Declarative Services component classes must define such a constructor. You define an activate() callback method to be invoked by the Declarative Services framework when the component is activated, along with a corresponding deactivate() callback method to be called when the component is deactivated.

 

Declarative Services callback methods

The names of callback methods (activate() and deactivate()) are defaults. If you wish to use a different pattern or are migrating legacy code, you can define the names of these callback methods via attributes on the <component> XML element. For example, the following code snippet redefines the activation and deactivation methods to be start() and stop(), respectively:

<component name="org.foo.example"
           activate="start"
           deactivate="stop">

The activation and deactivation methods are optional, so if your component has no need to track its activation state, you can leave them out. Also, if you use the default activate() and deactivate() method names, there’s no need to define these in the component declaration because the Declarative Services framework will discover them automatically.

Although it isn’t shown in the example, there’s also a callback method for the modified component lifecycle stage. Unlike the activation and deactivation methods, this lifecycle callback has no default method name, so you must define the method name in the <component> element, as follows:

<component name="org.foo.example" modified="modified">

This indicates that the component is interested in being notified about configuration updates and specifies the name of the callback method.

 

You may be wondering about the Map passed into the activate() method, which you use to configure the size of the component. The Declarative Services lifecycle callback methods accept a number of different argument types to give the component additional information, which we’ll describe next.

Lifecycle Callback Method Signatures

The lifecycle callback methods follow the same method-accessibility rules laid out earlier for binding methods. They may also accept zero or more of the following argument types (ordering isn’t important):

  • ComponentContext—Receives the component context for the component configuration
  • BundleContext—Receives the bundle context of the component’s bundle
  • Map—Receives an unmodifiable map containing the component’s properties
  • int or Integer—Receives the reason why the component is being deactivated, where the value is one of the following reasons:

    • 0—Unspecified.
    • 1—The component was disabled.
    • 2—A reference became unsatisfied.
    • 3—A configuration was changed.
    • 4—A configuration was deleted.
    • 5—The component was disposed.
    • 6—The bundle was stopped.

Of these arguments, you know little yet about ComponentContext. What is its purpose?

Using the ComponentContext

The Declarative Services framework creates a unique ComponentContext object for each component it activates. This object plays a role for components similar to the role the BundleContext object plays for bundles—it provides access to execution environment facilities. The ComponentContext interface is as follows:

public interface ComponentContext {
  public Dictionary getProperties();
  public Object locateService(String name);
  public Object locateService(String name, ServiceReference reference);
  public Object[] locateServices(String name);
  public BundleContext getBundleContext();
  public Bundle getUsingBundle();
  public ComponentInstance getComponentInstance();
  public void enableComponent(String name);
  public void disableComponent(String name);
  public ServiceReference getServiceReference();
}

The getProperties() method allows a component to access its configuration properties. The methods for locating services provide an alternative approach to service injection for using services; this alternative strategy is discussed in the “Lookup strategy” sidebar. The getBundleContext() method provides access to the containing bundle’s BundleContext object. The getUsingBundle() method is related to component factories, which we’ll discuss later.

The getComponentInstance(), enableComponent(), and disableComponent() methods provide a component with a way to programmatically control the component lifecycle; we’ll discuss them further in the next section. Finally, the getService-Reference() method allows a component to access the ServiceReference of this component if it provides a service.

In the general case, whether or not a component is satisfied is dictated by whether or not its service dependencies are satisfied. But this isn’t the only type of dependency considered by Declarative Services; another situation is where a component is dependent on its configuration properties.

 

Lookup strategy

So far in this section, we’ve shown you how to inject services into components using binding methods. This pattern is known as the Hollywood principle: “Don’t call us, we’ll call you.” In some circumstances, it’s useful to apply the Reverse Hollywood principle, “Do call us, we won’t call you.”

The Declarative Services specification supports both approaches; it refers to the injection approach as the event strategy and the alternative as the lookup strategy. The event strategy provides instant notification about service changes, whereas the lookup strategy is able to defer service creation until the last minute.

The family of locateService() methods on the ComponentContext facilitate the lookup strategy. These methods each take a String argument that specifies the name of the associated service reference in the component description XML file to retrieve. For example, you could change the paint frame description to be the following:

<scr:reference
  interface="org.foo.shape.SimpleShape"
  cardinality="0..n"
  policy="dynamic"
  name="shape"/>

In this case, you don’t have bind or unbind methods. To access any bound services, you need to use the ComponentContext, like this:

void findShapes(ComponentContext ctx) {
  Object[] services = ctx.locateServices("shape");
  for (Object s : services) {
    SimpleShape shape = (SimpleShape) s;
  }
}

It’s possible to mix and match these approaches. You can use the event strategy for some service references and the lookup strategy for others. You can even use a hybrid approach, using the event strategy with a binding method accepting a Service-Reference combined with the locateService(String, ServiceReference) method of the ComponentContext. This option provides a highly responsive but lightweight approach to service binding.

 

Configuration Policy

We’ve mentioned that it’s possible to configure a Declarative Services component by specifying an entry in the Configuration Admin Service with PID corresponding to the name of the component. These configuration properties override any specified in the XML description and provide a way to tweak the behavior of a component at execution time. It’s also possible to define a policy for how dependencies on configuration properties should be handled.

Declarative Services defines the following configuration policies: optional, require, and ignore. The default configuration policy is optional, indicating that Configuration Admin will be used if available. If require is specified, the component won’t be satisfied until there’s a configuration for it in Configuration Admin. The require policy is useful in cases where no sensible default value can be given for a configuration property (such as a database URL). The ignore policy indicates that only the declared component properties should be considered.

You specify configuration policies like this:

<component name="org.foo.example" configuration-policy="require">

In this example, the component requires a corresponding configuration to be present in Configuration Admin, or else it can’t be satisfied.

 

Configuration Admin Service factories

In chapter 9, you saw that it’s possible to use the Configuration Admin Service with one of two types of managed services, either a ManagedService or a Managed-ServiceFactory. The first takes a single configuration and uses it to configure a service provided by a bundle; the second allows multiple configurations to be created—each corresponding to a new service.

This pattern can be applied to Declarative Services components. If the component name matches a registered PID for a nonfactory configuration, the Declarative Services framework creates a single instance of the component. But if the component name matches a registered factory PID, a new component instance is created for each configuration associated with the factory PID. This provides a lightweight way of constructing several different component instances from a common component definition.

 

Components can be satisfied by the availability of their service dependencies and configuration, but the Declarative Services framework still won’t activate (instantiate) a component until another bundle requests its provided service. What about a component that doesn’t provide a service? How will it ever be activated?

Immediate VS. Delayed Components

Many components, such as the shape components in the paint example, exist solely to provide a function to other parts of an application. If no other deployed bundles consume the services these bundles provide, there’s no need to expend resources activating the associated components. Components that provide services are delayed by default in Declarative Services. Sometimes this delayed behavior is problematic. If you look again at the component declaration of the paint frame, you see that it specifies the immediate="true" attribute:

<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0"
  name="paint"
  immediate="true">

This turns off the delayed behavior and forces the Declarative Services implementation to construct the paint frame as soon as it’s satisfied. Because the paint frame component provides a service, it would be delayed by default. You need it to be immediate because you need it to create the window for the paint program. For components that don’t provide a service, it’s an error to set immediate to false because they would never be instantiated; instead, they’re implicitly defined as immediate components.

Component Factories

The final tool in the Declarative Services toolbox is component factories. These provide a programmatic mechanism to instantiate new instances of components. In many ways, this is similar to the factory PID mechanism of creating components mentioned earlier; but instead of going via the ConfigurationAdmin interface, the Declarative Services specification provides a mechanism to declare a component as explicitly providing a factory API, which is then manipulated by a secondary service to construct actual instances of the components.

To see how this works, let’s consider a slight variation of the original paint example where a shape component factory is registered to provide shape components on demand. You create a component factory by declaring the component using the factory=<factory.identifier> attribute on the top-level component declaration:

<scr:component xmlns:scr=http://www.osgi.org/xmlns/scr/v1.1.0
 factory="shape.factory" name="shape">

This results in the Declarative Services framework publishing a ComponentFactory service into the service registry. Component factory services can be used like any normal services; for example, client code wishing to be injected with this component factory can do the following:

  <reference
    name="shape"
    interface="org.osgi.service.component.ComponentFactory"
    target="(component.factory=shape.factory)"
    cardinality="1..1"
    policy="static"
    bind="addShapeFactory"
    unbind="removeShapeFactory"/>

Here the target attribute of the reference element is set to the name of the factory attribute of the declared component. To create a new shape instance, you use the following code.

Listing 11.7. Using a component factory

The component factory is registered with the ShapeManager component using callback methods . You use the factory.newInstance() method to instruct the Declarative Services runtime to build another instance of a shape with the specified name , which will then be registered in the OSGi registry as before.

 

Component factories vs. Configuration Admin Service factories

The component factory provides an alternative mechanism to the Configuration Admin managed service factory approach mentioned earlier. Which approach you take is largely a matter of preference. Note that the component factory approach and the managed service factory approach are mutually exclusive: it’s not possible to create a component factory that is instantiated by a factory PID.

 

This concludes our introduction to component models in OSGi and review of the Declarative Services specification. If you want a closer look at the Declarative Services version of the paint program, go to the chapter11/paint-example-ds/ directory of the companion code. Type ant to build the example and java -jar launcher.jar bundles to run it. The example uses the Apache Felix Service Component Runtime (SCR; http://felix.apache.org/site/apache-felix-service-component-runtime.html) implementation of Declarative Services.

11.4. Summary

In this chapter, we reviewed the general principles and motivation of component-oriented programming and looked at how components and modules intersect and interact in an OSGi context. The topics we discussed included the following:

  • Components are application building blocks that adhere to a component model.
  • Components further support separation of concerns by separating interface from implementation.
  • Components support external management of concerns, allowing you to offload mundane and potentially error-prone tasks to component frameworks.
  • The OSGi framework is a component framework, where bundles are equivalent to components that interact via services.
  • Additional, more advanced component frameworks can be layered on top of the OSGi framework to further enhance the core component model.
  • Declarative Services is an OSGi standard component framework that manages service publication, service dependencies, and configuration dependencies on behalf of components.

Component orientation in general and Declarative Services in particular are worthwhile approaches when you’re working with OSGi. In the next chapter, we’ll push even further by looking at two more advanced component frameworks, in case Declarative Services doesn’t address all your needs.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.144.121.45