Chapter 4. Studying services

 

This chapter covers

  • Understanding what services are and why they’re useful
  • Publishing and using services
  • Dealing with service dynamism
  • Modifying an application to use services
  • Relating services to the module and lifecycle layers

 

So far, you’ve seen two layers of the OSGi framework. The module layer helps you separate an application into well-defined, reusable bundles, and the lifecycle layer builds on the module layer to help you manage and evolve bundles over time. Now we’ll make things even more dynamic with the third and final layer of OSGi: services.

We’ll start this chapter with a general discussion about services to make sure we’re all thinking about the same thing. We’ll then look at when you should (and shouldn’t) use services and walk through an example to demonstrate the OSGi service model. At this point, you should understand the basics, so we’ll take a closer look at how best to handle the dynamics of OSGi services, including common pitfalls and how to avoid them.

With these techniques in mind, you’ll update the ongoing paint program to use services and see how the service layer relates to the module and lifecycle layers. We’ll conclude with a review of standard OSGi framework services and tell you more about the compendium. As you can see, we have many useful and interesting topics to cover, so let’s get started and talk about services.

4.1. The what, why, and when of services

Before looking at OSGi services, we should first explain what we mean by a service, because the term can mean different things to different people depending on their background. When you know the “what,” you also need to know why and when to use services, so we’ll get to that, too.

4.1.1. What is a service?

You may think a service is something you access across the network, like retrieving stock quotes or searching Google. But the classical view of a service is something much simpler: “work done for another.” This definition can easily apply to a simple method call between two objects, because the callee is doing work for the caller.

How does a service differ from a method call? A service implies a contract between the provider of the service and its consumers. Consumers typically aren’t worried about the exact implementation behind a service (or even who provides it) as long as it follows the agreed contract, suggesting that services are to some extent substitutable. Using a service also involves a form of discovery or negotiation, implying that each service has a set of identifying features (see figure 4.1).

Figure 4.1. Services follow a contract and involve some form of discovery.

If you think about it, Java interfaces provide part of a contract, and Java class linking is a type of service lookup because it “discovers” methods based on signatures and class hierarchy. Different method implementations can also be substituted by changing the JAR files on the class path. So a local method call could easily be seen as a service, although it would be even better if you could use a high-level abstraction to find services or if there was a more dynamic way to switch between implementations at execution time. Thankfully, OSGi helps with both by recording details of the service contract, such as interface names and metadata, and by providing a registry API to publish and discover services. You’ll hear more about this later, in section 4.2; for now, let’s continue to look at services in general.

You may be thinking that a Java method call in the same process can’t possibly be a service, because it doesn’t involve a remote connection or a distributed system. In reality, as you’ll see throughout this chapter, services do not have to be remote, and there are many benefits to using a service-oriented approach in a purely local application.

 

Components vs. services

When people discuss services, they often talk about components in the same context, so it’s useful to consider how services and components compare and overlap. Service-oriented design and component-oriented design are extremely complementary. The key semantic difference between these two approaches is as follows:

  • In a component-oriented approach, the architect focuses on the provider’s view.
  • In a service-oriented approach, the architect focuses on the consumer’s view.

Typically, in a component-oriented approach, the architect is focused on ensuring that the component they provide is packaged in such a way that it makes their life easier. You know that when it comes to packaging and deploying Java code, the code will often be used in a range of different scenarios. For example, a stock-quote program can be deployed as a console, GUI, or web application by combining different components. A component design approach tries to make it as easy as possible for the architect to select what functionality they want to deploy without hardcoding this into their application.

This contrasts with a service-oriented approach, where the architect is focused on supplying a function or set of functions to consumers who typically have little interest in how the internals of the individual component are constructed, but have specific requirements for how they want the function to behave. Examples include acid transactions, low latency, and encrypted data.

You’ll see in chapters 11 and 12 that component-oriented approaches can easily be built on top of the OSGi services model. With this in mind, let’s continue our introduction to services by considering the benefits of services.

 

4.1.2. Why use services?

The main drive behind using services is to get others to do work on your behalf, rather than attempting to do everything yourself. This idea of delegation fits in well with many object-oriented design techniques, such as Class-Responsibility-Collaboration (CRC) cards.[1] CRC cards are a role-playing device used by development teams to think about what classes they need, as well as which class will be responsible for which piece of work and how the various classes should collaborate to get work done.[2] Techniques like CRC cards try to push work out to other components wherever possible, which leads to lean, well-defined, maintainable components. Think of this like a game of pass-the-parcel (see figure 4.2), where each developer is trying to pass parcels of work to other developers—except in this game, when the music stops, you want the smallest pile of parcels!

1 Kent Beck and Ward Cunningham, “A Laboratory for Teaching Object-Oriented Thinking,” http://c2.com/doc/oopsla89/paper.html.

2 Don Wells, “Design a Simulator for the Coffee Maker,” www.extremeprogramming.org/example/crcsim.html.

Figure 4.2. Using CRC to place responsibilities can be like playing pass-the-parcel.

A service-oriented approach also promotes

  • Less coupling between providers and consumers, so it’s easier to reuse components
  • More emphasis on interfaces (the abstract) rather than superclasses (the concrete)
  • Clear descriptions of dependencies, so you know how it all fits together
  • Support for multiple competing implementations, so you can swap parts in and out

In other words, it encourages a plug-and-play approach to software development, which means much more flexibility during development, testing, deployment, and maintenance. You don’t mind where a service comes from, as long as it does what you want. Still not convinced? Let’s see how each of these points helps you build a better application.

Less Coupling

One of the most important aspects of a service is the contract. Every service needs some form of contract—otherwise, how could a consumer find it and use it (see figure 4.3)? The contract should include everything a consumer needs to know about the service, but no more. Putting too much detail in a contract tightens the coupling between provider and consumer and limits the possibility of swapping in other implementations later. To put it in clothing terms, you want it nice and stretchy to give your application room to breathe.

Figure 4.3. Why you need contracts

A good service contract clearly and cleanly defines the boundary between major components and helps with development and maintenance. After the contract is defined, you can work on implementing service providers and consumers in parallel to reduce development time, and you can use scripted or mock services to perform early testing of key requirements. Contracts are good news for everyone—but how do you define one in Java?

More Emphasis on Interfaces

Java interfaces can form part of a service contract. They list the various methods that make up a service along with expected parameters and return types. After they’re defined, you can begin programming against the agreed-on set of interfaces without having to wait for others to finish their implementations (see figure 4.4). Interfaces also have several advantages over concrete classes. A Java class can implement several interfaces, whereas it can only extend one concrete class. This is essential if you want flexibility over how you implement related services. Interfaces also provide a higher level of encapsulation because you’re forced to put logic and state in the implementing class, not the interface.

Figure 4.4. Programming to interfaces means teams can work in parallel.

You could stop at this point, assemble your final application by creating the various components with new, and wire their dependencies manually. Or you could use a dependency injection framework to do the construction and wiring for you. If you did, you’d have a pluggable application and all the benefits it entails, but you’d also miss out on two other benefits of a service-oriented approach: rich metadata and the ability to switch between implementations at execution time in response to events.

Clear Descriptions of Dependencies

Interfaces alone can’t easily capture certain characteristics of a service, such as the quality of a particular implementation or configuration settings like supported locales. Such details are often best recorded as metadata alongside the service interface, and to do this you need some kind of framework. Semantics, which describe what a service does, are also hard to capture. Simple semantics like pre- and post-conditions can be recorded using metadata or may even be enforced by the service framework. Other semantics can only be properly described in documentation, but even here metadata can help provide a link to the relevant information.

Think about your current application: what characteristics may you want to record outside of classes and interfaces? To get you started, table 4.1 describes some characteristics from real-world services that could be recorded as metadata.

Table 4.1. Example characteristics of real-world services

Characteristic

Why may you be interested?

Supported locales A price-checking service may only be available for certain currencies.
Transaction cost You may want to use the cheapest service, even if it takes longer.
Throughput You may want to use the fastest service, regardless of cost.
Security You may only want to use services that are digitally signed by certain providers.
Persistence characteristics You may only want to use a service that guarantees to store your data in such a way that it won’t be lost if the JVM restarts.

As you can see, metadata can capture fine-grained information about your application in a structured way. This is helpful when you’re assembling, supporting, and maintaining an application. Recording metadata alongside a service interface also means you can be more exact about what you need. The service framework can use this metadata to filter out services you don’t want, without having to load and access the service itself.

But why would you want to do this? Why not just call a method on the service to ask if it does what you need?

Support for Multiple Competing Implementations

A single Java interface can have many implementations; one may be fast but use a lot of memory, another may be slow but conserve memory. How do you know which one to use when they both implement the same interface? You could add a query method to the interface that tells you more about the underlying implementation, but that would lead to bloat and reduce maintainability. What would happen when you added another implementation that couldn’t be characterized using the existing method? Using a query method also means you have to find and call each service implementation before you know whether you want to use it, which isn’t efficient—especially when you may have hundreds of potential implementations that could be loaded at execution time.

Because service frameworks help you record metadata alongside services, they can also help you query and filter on this metadata when discovering services. This is different from classic dependency injection frameworks, which look up implementations based solely on the interfaces used at a given dependency point. Figure 4.5 shows how services can help you get exactly what you want.

Figure 4.5. Simple dependency injection vs. service discovery

We hope that, by now, you agree that services are a good thing—but as the saying goes, you can have too much of a good thing! How can you know when you should use a service or when it would be better to use another approach, such as a static factory method or simple dependency injection?

4.1.3. When to use services

The best way to decide when to use a service is to consider the benefits: less coupling, programming to interfaces, additional metadata, and multiple implementations. If you have a situation where any of these make sense or your current design provides similar benefits, you should use a service.

The most obvious place to use a service is between major components, especially if you want to replace or upgrade those components over time without having to rewrite other parts of the application. Similarly, anywhere you look up and choose between implementations is another candidate for a service, because it means you can replace your custom logic with a standard, recognized approach.

Services can also be used as a substitute for the classic listener pattern.[3] With this pattern, one object offers to send events to other objects, known as listeners. The event source provides methods to subscribe and unsubscribe listeners and is responsible for maintaining the list of listeners. Each listener implements a known interface to receive events and is responsible for subscribing to and unsubscribing from the event source (see figure 4.6).

3 Brian Goetz, “Java theory and practice: Be a good (event) listener,” www.ibm.com/developerworks/java/library/j-jtp07265/index.html.

Figure 4.6. Listener pattern

Implementing the listener pattern involves writing a lot of code to manage and register listeners, but how can services help? You can see a service as a more general form of listener, because it can receive all kinds of requests, not just events. Why not save time and get the service framework to manage listeners for you by registering them as services?

To find the current list of listeners, the sender queries the service framework for matching services (see figure 4.7). You can use service metadata to further define and filter the interesting events for a listener. In OSGi, this is known as the whiteboard pattern; you’ll use this pattern when you update the paint example to use services in section 4.4.

Figure 4.7. Whiteboard pattern

One downside of the whiteboard pattern is that it may not be clear that listeners should register a particular interface with the registry, but you can solve this by highlighting the interface in the event source’s documentation. It also introduces a dependency to the service framework, which you may not want for components that you want to reuse elsewhere. Finally, the service registry must be able to scale to large numbers of services, for situations where you have lots of sources and listeners.

4.1.4. When not to use services

Another way to decide if you should use services is to consider when you wouldn’t want to use them. Depending on the service framework, overhead may be involved when calling services, so you probably don’t want to use them in performance-critical code. That said, the overhead when calling a service in OSGi can be next to zero. You may have a one-time start-up cost, but calling a service is then just a direct method call. You should also consider the work required to define and maintain the service contract. There’s no point in using a service between two tightly coupled pieces of code that are always developed and updated in tandem (unless of course you need to keep choosing between multiple implementations).

4.1.5. Still not sure?

What if you’re still not sure whether to use a service? Fortunately, you can use an approach that makes development easier and helps you migrate to services later: programming to interfaces. If you use interfaces, you’re already more than halfway to using services, especially if you also take advantage of dependency injection. Of course, interfaces can be taken to extremes; there’s no point in creating an interface for a class if there will only ever be one implementation. But for outward-facing interaction between components, it definitely makes sense to use interfaces wherever possible.

What have you learned? You saw how interfaces reduce coupling and promote faster development, regardless of whether you end up using services. You also saw how services help capture and describe dependencies and how they can be used to switch between different implementations. More importantly, you learned how a service-oriented approach makes developers think more about where work should be done, rather than lump code all in one place. And finally, we went through a whole section about services without once mentioning remote or distributed systems.

Is OSGi just another service model? Should we end the chapter here with an overview of the API and move on to other topics? No, because one aspect is unique to the OSGi service model: services are completely dynamic.

4.2. OSGi services in action

What do we mean by dynamic? After a bundle has discovered and started using a service in OSGi, it can disappear at any time. Perhaps the bundle providing it has been stopped or even uninstalled, or perhaps a piece of hardware has failed; whatever the reason, you should be prepared to cope with services coming and going over time. This is different from many other service frameworks, where after you bind to a service it’s fixed and never changes—although it may throw a runtime exception to indicate a problem.

OSGi doesn’t try to hide this dynamism: if a bundle wants to stop providing a service, there’s little point in trying to hold it back or pretend the service still exists. This is similar to many of the failure models used in distributed computing. Hardware problems in particular should be acknowledged and dealt with promptly rather than ignored. Fortunately, OSGi provides a number of techniques and utility classes to build robust yet responsive applications on top of such fluidity; we’ll look more closely at these in chapters 11 and 12. But before we can discuss the best way to handle dynamic services, you first need to understand how OSGi services work at the basic level, and to do that we need to introduce the registry.

The OSGi framework has a centralized service registry that follows a publish-find-bind model (see figure 4.8). To put this in the perspective of service providers and consumers,

Figure 4.8. OSGi service registry

  • A providing bundle can publish Plain Old Java Objects (POJOs) as services.
  • A consuming bundle can find and then bind to services.

You access the OSGi service registry through the BundleContext interface, which you saw in section 3.2.4. Back then, we looked at its lifecycle-related methods; now we’ll look into its service-related methods, as shown in the following listing.

Listing 4.1. BundleContext methods related to services
public interface BundleContext {
  ...

  void addServiceListener(ServiceListener listener, String filter)
    throws InvalidSyntaxException;
  void addServiceListener(ServiceListener listener);
  void removeServiceListener(ServiceListener listener);
  ServiceRegistration registerService(
    String[] clazzes, Object service, Dictionary properties);
  ServiceRegistration registerService(
    String clazz, Object service, Dictionary properties);
  ServiceReference[] getServiceReferences(String clazz, String filter)
     throws InvalidSyntaxException;
  ServiceReference[] getAllServiceReferences(String clazz, String filter)
     throws InvalidSyntaxException;
  ServiceReference getServiceReference(String clazz);
  Object getService(ServiceReference reference);
  boolean ungetService(ServiceReference reference);

  ...
}

As long as your bundle has a valid context (that is, when it’s active), it can use services. Let’s see how easy it is to use a bundle’s BundleContext to publish a service.

4.2.1. Publishing a service

Before you can publish a service, you need to describe it so others can find it. In other words, you need to take details from the implemented contract and record them in the registry. What details does OSGi need from the contract?

Defining a Service

To publish a service in OSGi, you need to provide a single interface name (or an array of them), the service implementation, and an optional dictionary of metadata (see figure 4.9). Here’s what you can use for a service that provides both stock listings and stock charts for the London Stock Exchange (LSE):

Figure 4.9. Publishing a service that provides both stock listings and stock charts

String[] interfaces = new String[] {
    StockListing.class.getName(), StockChart.class.getName()};

Dictionary metadata = new Properties();
metadata.setProperty("name", "LSE");
metadata.setProperty("currency", Currency.getInstance("GBP"));
metadata.setProperty("country", "GB");

Class.getName() helps during refactoring. Note that metadata must be in the Dictionary type and can contain any Java type.

When everything’s ready, you can publish your service by using the bundle context:

ServiceRegistration registration =
    bundleContext.registerService(interfaces, new LSE(), metadata);

The registry returns a service registration object for the published service, which you can use to update the service metadata or to remove the service from the registry.

 

Note

Service registrations are private. They shouldn’t be shared with other bundles, because they’re tied to the lifecycle of the publishing bundle.

 

The LSE implementation is a POJO. It doesn’t need to extend or implement any specific OSGi types or use any annotations; it just has to match the provided service details. There’s no leakage of OSGi types into service implementations. You don’t even have to use interfaces if you don’t want to—OSGi will accept services registered under concrete class names, but this isn’t recommended.

Updating Service Metadata

After you’ve published a service, you can change its metadata at any time by using its service registration:

registration.setProperties(newMetadata);

This makes it easy for your service to adapt to circumstances and inform consumers about any such changes by updating its metadata. The only pieces of metadata that you can’t change are service.id and objectClass, which are maintained by the framework. Other properties that have special meaning to the OSGi framework are shown in table 4.2.

Table 4.2. Standard OSGi service properties

Key

Type

Description

objectClass String[] Class name the service was registered under. You can’t change it after registration.
service.id Long Unique registration sequence number, assigned by the framework when registering the service. You can’t choose or change it.
service.pid String Persistent (unique) service identifier, chosen by you.
service.ranking Integer Ranking used when discovering services. Defaults to 0. Services are sorted by their ranking (highest first) and then by their ID (lowest first). Chosen by you.
service.description String Description of the service, chosen by you.
service.vendor String Name of the vendor providing the service, chosen by you.
Removing a Service

The publishing bundle can also remove a published service at any time:

registration.unregister();

What happens if your bundle stops before you’ve removed all your published services? The framework keeps track of what you’ve registered, and any services that haven’t yet been removed when a bundle stops are automatically removed by the framework. You don’t have to explicitly unregister a service when your bundle is stopped, although it’s prudent to unregister before cleaning up required resources. Otherwise, someone could attempt to use the service while you’re trying to clean it up.

You’ve successfully published the service in only a few lines of code and without any use of OSGi types in the service implementation. Now let’s see if it’s just as easy to discover and use the service.

4.2.2. Finding and binding services

As with publishing, you need to take details from the service contract to discover the right services in the registry. The simplest query takes a single interface name, which is the main interface you expect to use as a consumer of the service:

ServiceReference reference =
    bundleContext.getServiceReference(StockListing.class.getName());

This time the registry returns a service reference, which is an indirect reference to the discovered service (see figure 4.10). This service reference can safely be shared with other bundles, because it isn’t tied to the lifecycle of the discovering bundle. But why does the registry return an indirect reference and not the actual service implementation?

Figure 4.10. Using an OSGi service

To make services fully dynamic, the registry must decouple the use of a service from its implementation. By using an indirect reference, it can track and control access to the service, support laziness, and tell consumers when the service is removed.

Choosing the Best Service

If multiple services match the given query, the framework chooses what it considers to be the “best” services. It determines the best service using the ranking property mentioned in table 4.2, where a larger numeric value denotes a higher-ranked service. If multiple services have the same ranking, the framework chooses the service with the lowest service identifier, also covered in table 4.2. Because the service identifier is an increasing number assigned by the framework, lower identifiers are associated with older services. So if multiple services have equal ranks, the framework effectively chooses the oldest service, which guarantees some stability and provides an affinity to existing services (see figure 4.11). Note that this only applies when you use getServiceReference—if you ask for multiple services using getServiceReferences, the ordering of the returned array is undefined.

Figure 4.11. OSGi service ordering (by highest service.ranking and then lowest service.id)

You’ve seen how to find services based on the interfaces they provide, but what if you want to discover services with certain properties? For example, in figure 4.12, if you ask for any stock listing service, you get back the first one (NYSE); but what if you want a UK-based listing? The bundle context provides another query method that accepts a standard LDAP filter string, described in RFC 1960,[4] and returns all services matching the filter.

4 T. Howes, “A String Representation of LDAP Search Filters,” www.ietf.org/rfc/rfc1960.txt.

Figure 4.12. Discovering an OSGi service

 

A quick guide to using LDAP queries

Perform attribute matching:

(name=John Smith)
(age>=20)
(age<=65)

Perform fuzzy matching:

(name~=johnsmith)

Perform wildcard matching:

(name=Jo*n*Smith*)

Determine if an attribute exists:

(name=*)

Match all the contained clauses:

(&(name=John Smith)(occupation=doctor))

Match at least one of the contained clauses:

(|(name~=John Smith)(name~=Smith John))

Negate the contained clause:

(!(name=John Smith))

 

Here’s how you can find all stock listing services using the GBP currency:

ServiceReference[] references =
    bundleContext.getServiceReferences(StockListing.class.getName(),
        "(currency=GBP)");

This returns references to the two LSE services (service.ids 3 and 4 in figure 4.12).

You can also use the objectClass property, mentioned in table 4.2, to query for services that provide specific additional interfaces. Here, you narrow the search to those stock listing services that use a currency of GBP and also provide a chart service:

ServiceReference[] references =
    bundleContext.getServiceReferences(StockListing.class.getName(),
        "(&(currency=GBP)(objectClass=org.example.StockChart))");

This returns only one LSE service reference (service.id 4 from figure 4.12) because the other LSE service provides listings, but not charts.

You can look up all sorts of service references based on your needs, but how do you use them? You need to dereference each service reference to get the actual service object.

Using a Service

Before you can use a service, you must bind to the actual implementation from the registry, like this:

StockListing listing =
    (StockListing) bundleContext.getService(reference);

The implementation returned is typically exactly the same POJO instance previously registered with the registry, although the OSGi specification doesn’t prohibit the use of proxies or wrappers.

 

Revisiting the magic method

Recall that in chapter 3, when you implemented the refresh command for the shell, you had to use the magic getPackageAdminService() method to acquire the Package Admin Service. Now you have enough knowledge to see what was happening behind the scenes:

private PackageAdmin getPackageAdminService() {
  return (PackageAdmin) m_context.getService(
    m_context.getServiceReference(
      PackageAdmin.class.getName()));
}

This method is simple—probably too simple, as you’ll find out later in section 4.3.1. You use the BundleContext to find a service implementing the Package Admin Service interface. This returns a service reference, which you use to get the service implementation. No more magic!

 

Each time you call getService(), the registry increments a usage count so it can keep track of who is using a particular service. To be a good OSGi citizen, you should tell the registry when you’ve finished with a service:

bundleContext.ungetService(reference);
listing = null;

 

Services aren’t proxies

In general in OSGi, when you’re making method calls on a service, you’re holding a reference to the Java object supplied by the providing bundle. For this reason, you should also remember to null variables referring to the service instance when you’re done using it, so it can be safely garbage collected. The actual service implementation should generally never be stored in a long-lived variable such as a field; instead, you should try to access it temporarily via the service reference and expect that the service may go away at any time.

 

You’ve now seen how to publish simple Java POJOs as OSGi services, how they can be discovered, and how the registry tracks their use. But if you remember one thing from this section, it should be that services can disappear at any time. If you want to write a robust OSGi-based application, you shouldn’t rely on services always being around or even appearing in a particular order when starting your application. Of course, we don’t want to scare you with all this talk of dynamism. It’s important to realize that dynamism isn’t created or generated by OSGi—it just enables it. A service is never arbitrarily removed; either a bundle has decided to remove it or an agent has stopped a bundle. You have control over how much dynamism you need to deal with, but it’s always good to code defensively in case things change in the future or your bundles are used in different scenarios.

What’s the best way to cope with potential dynamism? How can you get the most from dynamic services without continual checking and rechecking? The next section discusses potential pitfalls and recommended approaches when you’re programming with dynamic services.

4.3. Dealing with dynamics

In the last section, we covered the basics of OSGi services, and you saw how easy it is to publish and discover services. In this section, we’ll look more closely at the dynamics of services and techniques to help you write robust OSGi applications. To demonstrate, you’ll use the OSGi Log Service.

The Log Service is a standard OSGi service, one of the so-called compendium or non-core services. Compendium services will be covered more in section 4.6.2. For now, all you need to know is that the Log Service provides a simple logging facade, with various flavors of methods accepting a logging level and a message, as shown in the following listing.

Listing 4.2. OSGi Log Service
package org.osgi.service.log;

import org.osgi.framework.ServiceReference;

public interface LogService {

  public static final int LOG_ERROR   = 1;
  public static final int LOG_WARNING = 2;
  public static final int LOG_INFO    = 3;
  public static final int LOG_DEBUG   = 4;

  public void log(int level, String message);
  public void log(int level, String message,
       Throwable exception);

  public void log(ServiceReference sr, int level, String message);
  public void log(ServiceReference sr, int level, String message,
       Throwable exception);
}

With OSGi, you can use any number of possible Log Service implementations in the example, such as those written by OSGi framework vendors or others written by third-party bundle vendors. To keep things simple and to help you trace what’s happening inside the framework, you’ll use your own dummy Log Service that implements only one method and outputs a variety of debug information about the bundles using it.

 

Note

The examples in the next section are intended purely to demonstrate the proper usage of dynamic OSGi services. To keep these explanatory code snippets focused and to the point, they occasionally avoid using proper programming techniques such as encapsulation. You should be able to join the dots between the patterns we show you in these examples and real-world OO design. If you aren’t interested in the gory details of the OSGi service API and just want a simple, safe way to get services, skip ahead to the tracker example (section 4.3.3) or look at the component models in chapters 11 and 12.

 

You picked up the basics of discovering services in section 4.2.2. In the following section, you’ll take that knowledge and use it to look up and call the Log Service; we’ll point out and help you solve potential problems as we go along.

4.3.1. Avoiding common pitfalls

When people start using OSGi, they often write code that looks similar to the following listing.

Listing 4.3. Broken lookup example—service instance stored in a field

Because you store the Log Service instance in a field, the test code can be simple:

while (Thread.currentThread() == m_logTestThread) {
  m_logService.log(LogService.LOG_INFO, "ping");
  pauseTestThread();
}

But there’s a major problem with the bundle activator. The Log Service implementation is stored directly in a field, which means the consumer won’t know when the service is retracted by its providing bundle. It only finds out when the implementation starts throwing exceptions after removal, when the implementation becomes unstable. This hard reference to the implementation also keeps it from being garbage collected while the bundle is active, even if the providing bundle is uninstalled. To fix this, let’s replace the Log Service field with the indirect service reference, as shown in the following listing.

Listing 4.4. Broken lookup example—service is only discovered on startup

You also need to change the test method to always dereference the service, as in the following listing.

Listing 4.5. Broken lookup example—testing the discovered Log Service

This is slightly better, but there’s still a problem with the bundle activator. You discover the Log Service only once in the start() method, so if there is no Log Service when the bundle starts, the reference is always null. Similarly, if there is a Log Service at startup, but it subsequently disappears, the reference always returns null from that point onward. Perhaps you want this one-off check, so you can revert to another (non-OSGi) logging approach based on what’s available at startup. But this isn’t flexible. It would be much better if you could react to changes in the Log Service and always use the active one.

A simple way of reacting to potential service changes is to always look up the service just before you want to use it, as in the following listing.

Listing 4.6. Broken lookup example—potential race condition

With this change, the bundle activator becomes trivial and just records the context:

public class Activator implements BundleActivator {
  BundleContext m_context;
  public void start(BundleContext context) {
    m_context = context;
    startTestThread();
  }
  public void stop(BundleContext context) {
    stopTestThread();
  }
}

Unfortunately, you’re still not done, because there’s a problem in the test method—can you see what it is? Here’s a clue: remember that services can disappear at any time, and with a multithreaded application this can even happen between single statements.

The problem is that between the calls to getServiceReference() and get-Service(), the Log Service could disappear. The current code assumes that when you have a reference, you can safely dereference it immediately afterward. This is a common mistake made when starting with OSGi and an example of what’s more generally known as a race condition in computing. Let’s make the lookup more robust by adding a few more checks and a try-catch block, as in the following listing.

Listing 4.7. Correct lookup example

The test method is now robust but not perfect. You react to changes in the Log Service and fall back to other logging methods when there are problems finding or using a service, but you can still miss Log Service implementations. For example, imagine that a Log Service is available when you first call getServiceReference(), but it’s removed, and a different Log Service appears before you can use the original service reference. The getService() call returns null, and you end up not using any Log Service, even though a valid replacement is available. This particular race condition can’t be solved by adding checks or loops because it’s an inherent problem with the two-stage “find-then-get” discovery process. Instead, you must use another facility provided by the service layer to avoid this problem: service listeners.

4.3.2. Listening for services

The OSGi framework supports a simple but flexible listener API for service events. We briefly discussed the listener pattern back in section 4.1.3: one object (in this case, the framework) offers to send events to other objects, known as listeners. For services, there are currently three different types of event, shown in figure 4.13:

Figure 4.13. OSGi service events

  • REGISTERED—A service has been registered and can now be used.
  • MODIFIED—Some service metadata has been modified.
  • UNREGISTERING—A service is in the process of being unregistered.

Every service listener must implement this interface in order to receive service events:

public interface ServiceListener extends EventListener {
  public void serviceChanged(ServiceEvent event);
}

How can you use such an interface in the current example? You can use it to cache service instances on REGISTERED events and avoid the cost of repeatedly looking up the Log Service, as you did in section 4.3.1. A simple caching implementation may go something like the following listing.

Listing 4.8. Broken listener example—caching the latest service instance

It’s safe to call the getService() method during the REGISTERED event, because the framework delivers service events synchronously using the same thread. This means you know the service won’t disappear, at least from the perspective of the framework, until the listener method returns. Of course, the service could still throw a runtime exception at any time, but using getService() with a REGISTERED event always returns a valid service instance. For the same reason, you should make sure the listener method is relatively short and won’t block or deadlock; otherwise, you block other service events from being processed.

Registering a Service Listener

You have the service listener, but how do you tell the framework about it? The answer is, as usual, via the bundle context, which defines methods to add and remove service listeners. You must also choose an LDAP filter to restrict events to services implementing the Log Service; otherwise, you can end up receiving events for hundreds of different services. The final code looks like the following listing.

Listing 4.9. Broken listener example—existing services aren’t seen

The LDAP filter matches LogService instances, and you add a listener for future Log Service events. Notice that you don’t explicitly remove the service listener when you stop the bundle. This is because the framework keeps track of what listeners you’ve added and automatically cleans up any remaining listeners when the bundle stops. You saw something similar in section 4.2.1 when the framework removed any leftover service registrations.

The test method is now simple, because you’re caching the service instance:

while (Thread.currentThread() == m_logTestThread) {
  if (m_logService != null) {
    m_logService.log(LogService.LOG_INFO, "ping");
  } else {
    alternativeLog("LogService has gone");
  }
  pauseTestThread();
}

This looks much better, doesn’t it? You don’t have to do as much checking or polling of the service registry. Instead, you wait for the registry to tell you whenever a Log Service appears or disappears. Unfortunately, this code sample has a number of problems. First, there are some minor issues with the test method; you don’t catch runtime exceptions when using the service; and because of the caching, you don’t unget the service when you’re not using it. The cached Log Service could also change between the non-null test and when you use it.

More importantly, there’s a significant error in the listener code, because it doesn’t check that the UNREGISTERING service is the same as the Log Service currently being used. Imagine that two Log Services (A and B) are available at the same time, where the test method uses Log Service A. If Log Service B is unregistered, the listener will clear the cached instance even though Log Service A is still available. Similarly, as new Log Services are registered, the listener will always choose the newest service regardless of whether it has a better service ranking. To make sure you use the highest-ranked service and to be able to switch to alternative implementations whenever a service is removed, you must keep track of the current set of active service references—not just a single instance.

The bundle activator in listing 4.9 has another subtle error, which you may not have noticed at first. This error may never show up in practice, depending on how you start your application. Think back to how listeners work: the event source sends events to the listener as they occur. What about events that happened in the past? What about already-published services? In this case, the service listener doesn’t receive events that happened in the dim and distant past and remains oblivious to existing Log Service implementations.

Fixing the Service Listener

You have two problems to fix: you must keep track of the active set of Log Services and take into account already-registered Log Services. The first problem requires the use of a sorted set and relies on the natural ordering of service references, as defined in the specification of the compareTo() method. You’ll also add a helper method to decide which Log Service to pass to the client, based on the cached set of active service references; see the following listing.

Listing 4.10. Correct listener example—keeping track of active Log Services

Now the last service reference has the highest ranking.

You can fix the second problem in the bundle activator by issuing pseudo-registration events for each existing service, to make it look like the service has only just appeared, as shown in the following listing.

Listing 4.11. Correct listener example—sending pseudo-registration events

You deliberately lock the listener before passing it to the framework, so the pseudo-registration events are processed first. Otherwise, it would be possible to receive an UNREGISTERING event for a service before its pseudo-registration. Only when the listener has been added do you check for existing services, to make sure you don’t miss any intervening registrations. You could potentially end up with duplicate registrations by doing the checks in this order, but that’s better than missing services. The test method now only needs to call the helper method to get the best Log Service, as shown in the following listing.

Listing 4.12. Correct listener example—using the listener to get the best Log Service
while (Thread.currentThread() == m_logTestThread) {
  LogService logService = m_logListener.getLogService();

  if (logService != null) {
    try {
       logService.log(LogService.LOG_INFO, "ping");
    } catch (RuntimeException re) {
       alternativeLog("error in LogService " + re);
    }
  } else {
    alternativeLog("LogService has gone");
  }

  pauseTestThread();
}

You may have noticed that the finished listener example still doesn’t unget the service after using it; this is left as an exercise for you. Here’s a hint to get you started: think about moving responsibility for logging into the listener. This will also help you reduce the time between binding the service and using it.

Service listeners reduce the need to continually poll the service registry. They let you react to changes in services as soon as they occur and get around the inherent race condition of the find-then-get approach. The downside of listeners is the amount of code you need to write. Imagine having to do this for every service you want to use and having to repeatedly test for synchronization issues. Why doesn’t OSGi provide a utility class to do all this for you—a class that has been battle hardened and tested in many applications, that you can configure and customize as you require? It does, and the class’s name is ServiceTracker.

4.3.3. Tracking services

The OSGi ServiceTracker class provides a safe way for you to get the benefits of service listeners without the pain. To show how easy it can be, let’s take the bundle activator from the last example and adapt it in the following listing to use the service tracker.

Listing 4.13. Standard tracker example

In this example, you use the basic ServiceTracker constructor that takes a bundle context, the service type you want to track, and a customizer object. We’ll look at customizers in a moment; for now, you don’t need any customization, so you pass null. If you need more control over what services are tracked, there’s another constructor that accepts a filter.

 

Note

Before you can use a tracker, you must open it using the open() method to register the underlying service listener and initialize the tracked list of services. This is often the thing people forget to do when they first use a service tracker, and then they wonder why they don’t see any services. Similarly, when you’re finished with the tracker, you must close it. Although the framework automatically removes the service listener when the bundle stops, it’s best to explicitly call close() so that all the tracked resources can be properly cleared.

 

And that’s all you need to do to track instances of the Log Service—you don’t need to write your own listener or worry about managing long lists of references. When you need to use the Log Service, you ask the tracker for the current instance:

LogService logService = (LogService) m_logTracker.getService();

Other tracker methods get all active instances and access the underlying service references; there’s even a method that helps you wait until a service appears. Often, a raw service tracker is all you need, but sometimes you’ll want to extend it. Perhaps you want to decorate a service with additional behavior, or you need to acquire or release resources as services appear and disappear. You could extend the ServiceTracker class, but you’d have to be careful not to break the behavior of any methods you override. Thankfully, there’s a way to extend a service tracker without subclassing it: with a customizer object. The ServiceTrackerCustomizer interface shown here provides a safe way to enhance a tracker by intercepting tracked service instances:

public interface ServiceTrackerCustomizer {
  public Object addingService(ServiceReference reference);
  public void modifiedService(ServiceReference reference,
       Object service);
  public void removedService(ServiceReference reference,
       Object service);
}

Like a service listener, a customizer is based on the three major events in the life of a service: adding, modifying, and removing. The addingService() method is where most of the customization normally occurs. The associated tracker calls this whenever a matching service is added to the OSGi service registry. You’re free to do whatever you want with the incoming service; you can initialize some resources or wrap it in another object, for example. The object you return is tied to the service by the tracker and returned wherever the tracker would normally return the service instance. If you decide you don’t want to track a particular service instance, return null. The other two methods in the customizer are typically used for housekeeping tasks like updating or releasing resources.

Suppose you want to decorate the Log Service, such as adding some text around the log messages. The service tracker customizer may look something like the following listing.

Listing 4.14. Customized tracker example—decorated Log Service

All you have to do to decorate the Log Service is pass the customizer to the tracker:

m_logTracker = new ServiceTracker(context, LogService.class.getName(),
    new LogServiceDecorator());

Now any Log Service returned by this tracker will add angle brackets to the logged message. This is a trivial example, but we hope you can see how powerful customizers can be. Service tracker customizers are especially useful in separating code from OSGi-specific interfaces, because they act as a bridge connecting your application code to the service registry.

You’ve seen three different ways to access OSGi services: directly through the bundle context, reactively with service listeners, and indirectly using a service tracker. Which way should you choose? If you only need to use a service intermittently and don’t mind using the raw OSGi API, using the bundle context is probably the best option. At the other end of the spectrum, if you need full control over service dynamics and don’t mind the potential complexity, a service listener is best. In all other situations, you should use a service tracker, because it helps you handle the dynamics of OSGi services with the least amount of effort.

 

What? No abstractions?

If none of these options suit you, and you prefer to use a higher-level abstraction, such as components, this is fine too. As we mentioned at the start of this chapter, it’s possible to build component models on top of these core APIs. This is exactly what many people have been doing for the past few years, and several service-oriented component frameworks are based on OSGi; we’ll discuss them in chapters 11 and 12. But remember, all these component frameworks make subtle but important semantic choices when mapping components to the OSGi service model. If you need to cut through these abstractions and get to the real deal, now you know how.

 

Now that you know all about OSGi services and their dynamics, let’s look again at the paint program and see where it may make sense to use services.

4.4. Using services in the paint example

You last saw the paint example back in section 3.4, where you used an extender pattern to collect shapes. Why don’t you try using a service instead? A shape service makes a lot of sense, because you can clearly define what responsibilities belong to a shape and use metadata to describe various nonfunctional attributes like its name and icon. Remember that the first thing to define when creating a new service is the contract. What should a shape service look like?

4.4.1. Defining a shape service

Let’s use the previous interface as the basis of the new service contract—but this time, instead of extension names, you’ll declare service property names. These names will tell the client where to find additional metadata about the shape:

public interface SimpleShape {

  public static final String NAME_PROPERTY = "simple.shape.name";
  public static final String ICON_PROPERTY = "simple.shape.icon";

  public void draw(Graphics2D g2, Point p);
}

This isn’t much different from the interface defined in section 3.4. You can see how easy it is to switch over to services when you’re programming with interfaces. With this contract in hand, you now need to update each shape bundle to publish its implementation as a service, and update the paint frame bundle to track and consume these shape services.

4.4.2. Publishing a shape service

Before you can publish a shape implementation as a service, you need a bundle context. To get the bundle context, you need to add a bundle activator to each shape bundle, as shown in the following listing.

Listing 4.15. Publishing a shape service

You record the name and icon under their correct service properties. The shape bundles will now publish their shape services when they start and remove them when they stop. To use these shapes when painting, you need to update the paint frame bundle so it uses services instead of bundles, as shown in figure 4.14.

Figure 4.14. Painting with services

4.4.3. Tracking shape services

Remember the DefaultShape class that acted as a simple proxy to an underlying shape bundle in section 3.4? When the referenced shape bundle was active, the DefaultShape used its classes and resources to paint the shape. When the shape bundle wasn’t active, the DefaultShape drew a placeholder image instead. You can use the same approach with services, except that instead of a bundle identifier, you use a service reference as shown here:

if (m_context != null) {
  try {
    if (m_shape == null) {
      m_shape = (SimpleShape) m_context.getService(m_ref);
    }
    m_shape.draw(g2, p);
    return;
  } catch (Exception ex) {}
}

This code gets the referenced shape service and draws a shape with a simple method call. A placeholder image is drawn instead if there’s a problem.

You can also add a dispose() method to tell the framework when you’re finished with the service:

public void dispose() {
  if (m_shape != null) {
    m_context.ungetService(m_ref);
    m_context = null;
    m_ref = null;
    m_shape = null;
  }
}

The new DefaultShape is now based on an underlying service reference, but how do you find such a reference? Remember the advice from section 4.3.3: you want to use several instances of the same service and react as they appear and disappear, but you don’t want detailed control—you need a ServiceTracker.

In the previous chapter, you used a BundleTracker to react as shape bundles came and went. This proved to be a good design choice, because it meant the ShapeTracker class could process shape events and trigger the necessary Swing actions. All you need to change is the source of shape events, as shown in the following listing; they now come from the ServiceTracker methods.

Listing 4.16. Sending shape events from ServiceTracker methods

The processing code also needs to use service properties rather than extension metadata:

String name = (String) ref.getProperty(SimpleShape.NAME_PROPERTY);
Icon icon = (Icon) ref.getProperty(SimpleShape.ICON_PROPERTY);

And that’s all there is to it. You now have a service-based paint example. To see it in action, go into the chapter04/paint-example/ directory of the companion code, type ant to build it, and type java -jar launcher.jar bundles to run it. The fact that you needed to change only a few files is a testament to the non-intrusiveness of OSGi services if you already use an interface-based approach.

We hope you can also see how easy it would be to do this in reverse and adapt a service-based example to use extensions. Imagine being able to decide when and where to use services in your application, without having to factor them into the initial design. The OSGi service layer gives you that ability, and the previous layers help you manage and control it. But how can the module and lifecycle layers help; how do they relate to the service layer?

4.5. Relating services to modularity and lifecycle

The service layer builds on top of the module and lifecycle layers. You’ve already seen one example, where the framework automatically unregisters services when their registering bundle stops. But the layers interact in other ways, such as providing bundle-specific (also known as factory) services and specifying when you should unget and unregister services, and how you should bundle up services. But let’s start with how modularity affects what services you can see.

4.5.1. Why can’t I see my service?

Sometimes you may ask yourself this question and wonder why, even though the OSGi framework shows a particular service as registered, you can’t access it from your bundle. The answer comes back to modularity. Because multiple versions of service interface packages may be installed at any given time, the OSGi framework only shows your bundle services using the same version. The reasoning behind this is that you should be able to cast service instances to any of their registered interfaces without causing a ClassCastException.

But what if you want to query all services, regardless of what interfaces you can see? Although this approach isn’t common, it’s useful in management scenarios where you want to track third-party services even if they aren’t compatible with your bundle. To support this, the OSGi framework provides a so-called All* variant of the getService-References() method to return all matching services, regardless of whether their interfaces are visible to the calling bundle. For example:

ServiceReference[] references =
    bundleContext.getAllServiceReferences(null, null);

This returns references to all services currently registered in the OSGi service registry. Similarly, for service listeners there’s an All* extension of the ServiceListener interface, which lets you receive all matching service events. The ServiceTracker is the odd one out, with no All* variant—to ignore visibility, you start the tracker with open(true).

You’ve seen that although one bundle can see a service, another bundle with different imports may not. How about two bundles with the same imports? They see the same service instances. What if you want them to see different instances—is it possible to customize services for each consumer?

4.5.2. Can I provide a bundle-specific service?

You may have noticed that throughout this chapter, you’ve assumed that service instances are created first and then published, discovered, and finally used. Or, to put it another way, creation of service instances isn’t related to their use. But sometimes you want to create services lazily or customize a service specifically for each bundle using it. An example is the simple Log Service implementation from section 4.3. None of the Log Service methods accept a bundle or bundle context, but you may want to record details of the bundle logging the message. How is this possible in OSGi? Doesn’t the registerService() method expect a fully constructed service instance?

The OSGi framework defines a special interface to use when registering a service. The ServiceFactory interface acts as a marker telling the OSGi framework to treat the provided instance not as a service, but as a factory that can create service instances on demand. The OSGi service registry uses this factory to create instances just before they’re needed, when the consumer first attempts to use the service. A factory can potentially create a number of instances at the same time, so it must be thread safe:

public interface ServiceFactory {
  public Object getService(Bundle bundle,
      ServiceRegistration registration);
  public void ungetService(Bundle bundle,
      ServiceRegistration registration, Object service);
}

The framework caches factory-created service instances, so a bundle requesting the same service twice receives the same instance. This cached instance is removed only when the bundle has completely finished with a service (that is, the number of calls to get it match the calls to unget it), when the bundle has stopped, or when the service factory is unregistered. Should you always unget a service after you use it, like closing an I/O stream?

4.5.3. When should I unget a service?

You just saw that instances created from service factories are cached until the consuming bundle has finished with the service. This is determined by counting the calls to getService() compared to ungetService(). Forgetting to call unget can lead to instances being kept around until the bundle is stopped. Similarly, agents interrogating the framework will assume the bundle is using the service when it isn’t. Should you always unget after using a service, perhaps something like the following?

try {
  Service svc = (Service) m_context.getService(svcRef);
  if (svc != null) {
     svc.dispatch(something);
  } else {
     fallback(somethingElse);
  }
} finally {
  m_context.ungetService(svcRef);
}

This code records exactly when you use the service, but what happens if you want to use it again and again in a short space of time? Services backed by factories will end up creating and destroying a new instance on every call, which can be costly. You may also want to keep the instance alive between calls if it contains session-related data. In these circumstances, it makes more sense to get at the start of the session and unget at the end of the session. For long-lived sessions, you still need to track the service in case it’s removed, probably using a service tracker customizer to close the session. In all other situations, you should unget the service when you’re finished with it.

But what about the other side of the equation? Should bundles let the framework unregister their services when they stop, or should they be more proactive and unregister services as soon as they don’t want to or can’t support them?

4.5.4. When should I unregister my service?

The OSGi framework does a lot of tidying up when a bundle stops—it removes listeners, releases used services, and unregisters published services. It can often feel like you don’t need to do anything yourself; indeed, many bundle activators have empty stop() methods. But sometimes it’s prudent to unregister a service yourself. Perhaps you’ve received a hardware notification and need to tell bundles not to use your service. Perhaps you need to perform some processing before shutting down and don’t want bundles using your service while this is going on. At times like this, remember that you’re in control, and it’s often better to be explicit than to rely on the framework to clean up after you.

After that salutary message, let’s finish this section with a modularity topic that has caused a lot of heated discussion on OSGi mailing lists: where to package service interfaces.

4.5.5. Should I bundle interfaces separately?

Service interfaces are by definition decoupled from their implementations. Should they be packaged separately in their own bundle or duplicated inside each implementation bundle? OSGi supports both options, because as long as the metadata is correct, it can wire the various bundles together to share the same interface. But why would you want to copy the interface inside each implementation bundle? Surely that would lead to duplicated content.

Think about deploying a set of services into a framework. If each service has both an API and an implementation bundle, that doubles the number of bundles to manage. Putting the interface inside the implementation bundle means you need to provide only one JAR file. Similarly, users don’t have to remember to install the API—if they have the implementation, they automatically get the API for free. This sounds good, so why doesn’t everyone do it?

It comes down to managing updates. Putting interfaces inside an implementation bundle means the OSGi framework may decide to use that bundle as the provider of the API package. If you then want to upgrade and refresh the implementation bundle, all the consuming bundles will end up being refreshed, causing a wave of restarting bundles. Similarly, if you decide to uninstall the implementation, the implementation classes will be unloaded by the garbage collector only when the interface classes are no longer being used (because they share the same class loader).

In the end, there’s no single right answer. Each choice has consequences you should be aware of. Just as with other topics we’ve discussed—service visibility, service factories, and using unget and unregister—you need to know the possibilities to make an informed choice. We’ll come back to this topic in the next chapter, because packaging service interfaces with the implementation bundle also requires you to define the bundle metadata a little differently. Whatever you decide, we can all agree that services are an important feature of OSGi.

4.6. Standard services

Services are such an important feature that they’re used throughout the OSGi specification. By using services to extend the framework, the core API can be kept lean and clean. Almost all extensions to OSGi have been specified as optional add-on services without requiring any changes to the core specification. These standard OSGi services are divided into two categories: core and compendium. We’ll take a quick look at some of the core and compendium services in the next two subsections (see table 4.3).

Table 4.3. Standard OSGi services covered in this section

Service

Type

Description

Package Admin Core Manages bundle updates and discovers who exports what
Start Level Core Queries and controls framework and bundle start levels
URL Handlers Core Handles dynamic URL streams
Permission Admin Core Manages bundle and service permissions
HTTP Compendium Puts simple servlets and resources onto the web
Event Admin Compendium Provides a topic-based publish-subscribe event model
Configuration Admin Compendium Manages and persists configuration data
User Admin Compendium Performs role-based authentication and authorization

4.6.1. Core services

The following core services are generally implemented by the OSGi framework itself, because they’re intimately tied to framework internals.

Package Admin Service

The OSGi Package Admin Service, which we discussed in chapter 3, provides a selection of methods to discover details about exported packages and the bundles that export and/or import them. You can use this service to trace dependencies between bundles at execution time, which can help when upgrading because you can see what bundles may be affected by the update. The Package Admin Service also provides methods to refresh exported packages, which may have been removed or updated since the last refresh, and to explicitly resolve specific bundles.

Start Level Service

The OSGi Start Level Service lets you programmatically query and set the start level for individual bundles as well as the framework itself, which allows you to control the relative order of bundle activation. You can use start levels to deploy an application or roll out a significant update in controlled stages. We’ll discuss this more in chapter 10.

Url Handlers Service

The OSGi URL Handlers Service adds a level of dynamism to the standard Java URL process. The Java specification unfortunately only allows one URLStreamHandler-Factory to be set during the lifetime of a JVM, so the framework attempts to set its own implementation at startup. If this is successful, this factory dynamically provides URL stream handlers and content handlers, based on implementations registered with the OSGi service registry.

(Conditional) Permission Admin Service

Two OSGi services deal with permissions: the Permission Admin Service, which deals with permissions granted to specific bundles, and the Conditional Permission Admin Service, which provides a more general-purpose and fine-grained permission model based on conditions. Both of these services build on the standard Java 2 security architecture. We’ll discuss security more in chapter 14.

You now know which core services you can expect to see in an OSGi framework, but what about the non-core compendium services? What sort of capabilities do they cover?

4.6.2. Compendium services

In addition to the core services, the OSGi Alliance defines a set of non-core standard services called the compendium services. Whereas the core services are typically available by default in a running OSGi framework, the compendium services aren’t. Keeping with the desire for modularity, you wouldn’t want them to be included by default because this would lead to bloated systems. Instead, these services are provided as separate bundles by framework implementers or other third parties and typically work on all frameworks.

You’ve already seen one example of a compendium service: the Log Service from section 4.3, which provides a simple logging API. This is one of the better-known compendium services. Let’s take a brief look at other popular examples.

Http Service

The OSGi HTTP Service supports registration of servlets and resources under named aliases. These aliases are matched against incoming URI requests, and the relevant servlet or resource is used to construct the reply. You can authenticate incoming requests using standard HTTP/HTTPS, the OSGi User Admin Service, or your own custom approach. The current HTTP Service is based on version 2.1 of the servlet specification,[5] which means it doesn’t cover servlet filters, event listeners, or JSPs. Later versions of the HTTP Service specification should address this, and some implementations already support these additional features.[6] We’ll talk more about the HTTP Service and OSGi web applications in chapter 15.

5http://java.sun.com/products/servlet/2.1/servlet-2.1.pdf.

6 For example, Pax Web: http://wiki.ops4j.org/display/paxweb.

Event Admin Service

The OSGi Event Admin Service provides a basic publish-subscribe event model. Each event consists of a topic, which is basically a semistructured string, and a set of properties. Event handlers are registered as services and can use metadata to describe which topics and properties they’re interested in. Events can be sent synchronously or asynchronously and are delivered to matching event handlers by using the whiteboard pattern, which we discussed in section 4.1.3. Other types of OSGi events (like framework, bundle, service, and log events) are mapped and republished by the Event Admin Service implementation.

Configuration Admin Service

The OSGi Configuration Admin Service delivers configuration data to those services with persistent identifiers (service.pid) that implement the ManagedService interface—or ManagedServiceFactory, if they want to create a new service instance per configuration. These so-called configuration targets accept configuration data in the form of a dictionary of properties. Management bundles, which have been granted permission to configure services, can use the Configuration Admin Service to initialize and update configurations for other bundles. Nonmanagement bundles can only update their own configurations. The Configuration Admin Service is pluggable and can be extended by registering implementations of the ConfigurationPlugin interface with the OSGi service registry. chapter 9 provides detailed examples of how to supply and consume configuration data.

User Admin Service

The OSGi User Admin Service provides a role-based model for authentication (checking credentials) and authorization (checking access rights). An authenticating bundle uses the User Admin Service to prepopulate the required roles, groups, and users along with identifying properties and credentials. This bundle can query the User Admin Service at a later date to find users, check their credentials, and confirm their authorized roles. It can then decide how to proceed based on the results of these checks.

This is a short sample of the compendium services; you can find a complete table in appendix B. You can also read detailed specifications of each service in OSGi Service Platform Service Compendium.[7]

7 OSGi Alliance, OSGi Service Platform Service Compendium (2009), www.osgi.org/download/r4v42/r4.cmpn.pdf

4.7. Summary

That was a lot of information to digest, so don’t worry if you got a bit woozy. Let’s summarize this chapter:

  • A service is “work done for another.”
  • Service contracts define responsibilities and match consumers with providers.
  • Services encourage a relaxed, pluggable, interface-based approach to programming.
  • You don’t need to care where a service comes from as long as it meets the contract.
  • The best place to use services is between replaceable components.
  • Think carefully before using services in tightly coupled or high-performance code.
  • Services can replace the listener pattern with a much simpler whiteboard pattern.
  • OSGi services use a publish-find-bind model.
  • OSGi services are truly dynamic and can appear or disappear at any time.
  • The easiest and safest approach is to use the OSGi ServiceTracker utility class.
  • For higher-level service abstractions, consider the component models in chapters 11 and 12.
  • OSGi services build on and interact with the previous module and lifecycle layers.
  • OSGi defines core framework services and additional compendium services.

Services aren’t limited to distributed or remote applications. There’s a huge benefit to applying a service-oriented design to a purely local, single-JVM application, and we hope you get the opportunity to experience this in your next project.

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

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