Blueprint and Service Dynamism in OSGi

This article is based on Enterprise OSGi in Action, to be published on March 2012. It is being reproduced here by permission from Manning Publications. Manning publishes MEAP (Manning Early Access Program,) eBooks and pBooks. MEAPs are sold exclusively through Manning.com. All pBook purchases include free PDF, mobi and epub. When mobile formats become available all customers will be contacted and upgraded. Visit Manning.com for more information. [ Use promotional code 'java40beat' and get 40% discount on eBooks and pBooks ]

also read:

OSGi is a dynamic environment. Stopping a bundle fragment removed translations, and stopping and starting bundles made special offers appear and disappear. This dynamism is amazingly powerful and allows OSGi applications to do things that aren’t possible for conventional applications.

The remarkable appearing and disappearing services

What this dynamism really means is that services can come and go at any time. It’s possible that a required service isn’t available, that there’s than one match for a required service, that a new service becomes available at runtime, or that a service which is in use goes away. Put like that, OSGi dynamism sounds a bit scary. How can you write a solid program on quicksand?

Figure 1 It’s possible that (a) a required service isn’t available, (b) that there’s than one match for a required service or that a new service becomes available at runtime, or (c) that a service that is in use goes away.
Luckily, your application is only as dynamic as you want it to be. If you have good control over your application environment and have no need to add or remove services on the fly and no desire to upgrade your application without restarting everything you can probably assume that a lot of the scenarios in figure 1 won’t apply to you.

Be careful, though—even relatively static applications are not completely free from OSGi dynamics. Even if your application never changes once it’s installed, there will be a period during server startup and shutdown when not every service is available to everyone who might want it. In general, OSGi bundle start order is undefined, so your services may not appear in the order you expected.

The good news is that OSGi Blueprint insulates you from almost all of this uncertainty, both during bundle startup and later during runtime.

Startup and grace period

When a bundle starts, there’s no guarantee that other bundles within the application which it requires have already been started. (The start order on your test system may end up being quite different from the start order on the customer’s production box.)

What happens when a bundle declares Blueprint beans that depend on services from other bundles but those services aren’t there? Starting the bean anyway and hoping the services eventually appear would be pretty rash. On the other hand, not starting the bean at all could mean that beans never start if the start order isn’t exactly perfect. Blueprint compromises by allowing a grace period for required services to appear.

Each bundle with Blueprint component has its own Blueprint Container. (This is probably a bit at odds with your mental model, which may have a single Blueprint Container for the whole system.) The Blueprint Container for a bundle will not start unless all the mandatory dependencies are available.

By default, the Blueprint Container will wait five minutes for all required services to appear. You may see messages relating to GRACE_PERIOD in the log during this time. If all the required services don’t appear, the container won’t start for the dependent bundle. You can change the duration of the grace period or even eliminate it entirely, but we can’t think of many good reasons why you’d want to!

Figure 2 The Blueprint Container reporting that it is waiting for a Blueprint dependency to become availableWARNING: THE IMPACT OF ONE MISSING SERVICE

If any service dependency of a bundle can’t be satisfied, the Blueprint Container won’t start. This means no Blueprint services will be registered by that bundle and there will be no managed Blueprint beans for the bundle. You may not have expected that a missing service dependency for one bean would prevent all your other beans from running. If you’re hitting problems in this area, consider declaring some of your service dependencies optional or dividing your bundle into smaller independent bundles. If you have several independent dependency networks in the same bundle, it may be a sign that you haven’t got the modularity quite right.

Blueprint also has an elegant way of handling services which appear, disappear, or change at runtime. Like the startup solution, it’s basically a sophisticated variant of container-managed wait and hope. It’s a lot more robust than that description makes it sound, and it’s also more granular than the startup case! This technology is known as damping.

Damping

OSGi Blueprint aims to reduce the complexity of consuming OSGi services by performing service damping rather than injecting a managed bean with a service, the Blueprint Container injects a proxy. This means that if the service implementation needs to be replaced then this can be performed transparently, without interrupting the normal operation of the bean. Figure 2 shows how a proxy allows the provider to be transparently swapped.

Figure 2 If the proxied implementation of a Blueprint-provided service becomes unavailable, the proxy will be transparently wired to another implementation.
If no other implementation is available, any services registered by the consuming bean will automatically be unregistered. This management of dependency chains is one of the benefits of using Blueprint. You can see this behavior in action in the following sample Fancy Foods application by stopping the persistence bundle. The Blueprint Container will automatically unregister the desperate cheese special offer, since that special offer required the persistence service.

Figure 3 If the persistence bundle is stopped, the Blueprint Container automatically makes the cheese special offer unavailable and removes it from the reference list in the offer aggregator.
The offer aggregator uses a reference list rather than a single reference. The cheese service is transparently removed from the aggregator list when it is unregistered, and the aggregator service itself is not affected.

Timeouts

What happens when an attempt is made to use an injected service after it goes away? Nothing…literally! If a method is invoked on the service proxy, the proxy will block until a suitable service becomes available or until a timeout occurs. On timeout, a ServiceUnavailableException will be thrown.

In practice, timeouts don’t occur very often because the automatic unregistration of chained dependencies eliminates most opportunities to use nonexistent services. What’s required is a bit of surgery so that you can stop a service before it has been used, but after any services which depend on it have been invoked. What we’re trying to simulate here is a minor concurrency disaster rather than normal operation.

In the sample application, we modify the getOfferFood() method on the DesperateCheeseOffer to insert a pause so that you can reliably stop the persistence bundle in the middle of the method execution:

public Food getOfferFood() {
		long start = System.currentTimeMillis();
		System.out.println("INVOKED, PAUSING, STOP THAT BUNDLE!");
		try {
			Thread.sleep(5000);
		} catch (InterruptedException e) {
	}
	System.out.println("RESUMING after "
	+ (start - System.currentTimeMillis()) + " ms");
	List cheeses = inventory.getFoodsWhoseNameContains("cheese", 1);
	System.out.println("Cheese returned after "
	+ (start - System.currentTimeMillis()) + " ms.");
	Food leastPopularCheese = cheeses.get(0);//
	return leastPopularCheese;

Rebuild the cheese bundle and drop it into the load directory. Also update the OfferAggregator to disable the sorting of the offer list for the moment and rebuild the business bundle. Hit the web page and then stop the persistence bundle once you see your first message appearing in the console. You’ll see that instead of returning after the five second pause you introduced, the load request on the web page hasn’t returned. What’s going on? The call on the inventory object will block until a persistence provider becomes available again or until the Blueprint timeout happens after five minutes (figure 4).

Figure 4 If a Blueprint service is unregistered after services that depend on it have been invoked, the proxy to the service will block for five minutes before throwing a ServiceUnavailableException.
The really nice thing about the Blueprint damping is that the disappearance of a service need not be fatal. If you restart the persistence bundle before the five minute timeout expires, the cheese offer will miraculously kick back into life. However, you may get more than you bargained for, as figure 5 shows.

Figure 5 If a proxied service reappears after disappearing, blocked service calls will resume and complete almost normally. However, dependent services will be removed and re-registered, which could allow iterators to traverse them twice.
What’s going on? We certainly didn’t expect that the removal of the cheese offer service would give us two cheese offers.

The list of offers is injected into the OfferAggregator, which passes a reference to the same list to the servlet. When the servlet starts iterating over the list, there are two elements in the list. It requests the details for the chocolate offer, and then it requests the details for the cheese offer, which is where you disable the persistence bundle, causing the cheese offer to block when asked for its details.

Re-enabling the persistence bundle unblocks the call and allows the servlet to see the details of the cheese offer. Meanwhile, however, the cheese offer has been removed from the aggregator’s reference list and then re-added to it. When the iterator finishes printing out the details of the cheese offer, it checks to see if there are any more offers and discovers that a new offer has been added to the list—a cheese offer, apparently. At the end of the procedure, the reference list still only has two offers, cheese and chocolate, but if the timing is just right (or you insert lots of pauses, as we did!), an unlucky iterator could see both the old and new occurrences of the cheese offer.

In this case, the only consequence of a potentially disastrous outage of the persistence bundle is that the cheese offer is included twice in the printed list of offers. It looks a bit stupid, but there’s no harm done. If iterating over the same service twice is more serious, you may want to consider making unmodifiable copies of reference lists before passing them around or iterating over them.

WARNING In our opinion, this is incredibly cool. We must be honest, though, and disclose that it took a few tries to get this example right! If you expect to support these extreme dynamics in your application, it’s’ important that you test properly. Hanging on to references to services which might have been compromised can cause problems. In this case if there were multiple calls to from getOfferFood() the web front end or the offer aggregator sorting routine a ServicedUnavailableException was thrown from the second call.

Setting timeouts

The default Blueprint timeout is five minutes, but it can be adjusted per bundle or per reference. For example, the following update to the Inventory reference ensures that method invocations never block for more than 2 seconds, even when no Inventory service is available:

<reference id="inventory" interface="fancyfoods.food.Inventory" timeout="2000" />

Timeouts can also be set for an entire bundle in the manifest by setting directives on the symbolic name:

Bundle-SymbolicName: fancyfoods.persistence; Blueprint.timeout=2000

If the timeout is set to 0, a service invocation will block indefinitely.

Multiplicity and optionality

When declaring a Blueprint service reference, one of the things you can do is declare the service optional.

<reference id="specialOffer" interface="fancyfoods.offers.SpecialOffer" availability="optional"/>

This means that if no satisfactory service can be found, the Blueprint Container will still start up and initialize the rest of your beans. You need to be careful to check any optional services in your beans are not null before using them for the first time. But what happens if that service goes away once the Blueprint Container has started?

Service damping and optional references

The answer may not be what you’d expect when you think of an optional service. Once the service has been injected, it’s there to stay, and it won’t suddenly become null again. Even if the backing service goes away, any method invocation on the injected service (which is a proxy, remember) will just block until the service reappears, or until a timeout happens. This is true even if the reference was initially declared optional—your optional service has now become a mandatory service!

So how can you make a service optional at runtime rather than just startup time? One option is to set a very short timeout and catch the ServiceUnavailableException. A cleaner option is to use a reference listener to monitor the lifecycle of the service (more on those in a moment). Alternatively, the slightly sneaky option is to use a reference list instead of a reference for your service.

So far, we’ve been talking about how damping works for service references. Reference lists are subtly different from references in how they handle dynamism, and sometimes this difference comes in handy.

References and reference-lists
A reference refers to one and only one instance of a service. Reference lists, on the other hand, refer to several instances of a service. Although reference lists still use proxies, they make it much more apparent when services come and go. As services appear and disappear from the system, they will pop in and out of a reference list. So dynamic is the reference list, services can even appear in an existing iterator. (No ConcurrentModificationExceptions here!)

Figure 6 As services appear and disappear in a system, they are automatically added to or removed from reference lists.
How does this help with optional services? If you use a reference list rather than a reference to inject a service, the service’s absence won’t prevent the bundle’s Blueprint Container from starting and, if the service goes away at runtime, you’ll simply have a zero length iterator.

Coping with multiplicity

On the other hand, if you use a reference list, you may get more than you bargained for. What will you do if multiple instances of a service are present? You could decide you never want more than one, and only ever look at the first element in the list. But how do you know the first service will be the most appropriate one?

It turns out that this is an issue which you’ll have to think about even if you’re using references instead of reference lists. In that case, the container will choose the service for you. But, how does it decide which service is best for you?

One strategy for ensuring you get supplied with the most suitable service is to specify a ranking for each service. The downside here is that the ranking must be specified at the time the service is registered by the service provider. If that’s not you, you’ll have some work to do persuading all your dependencies to rank themselves. More seriously, what should the ranking even be? Can you be sure that every consumer of the services will agree on the same relative ranking? Should a faster service implementation be ranked highest or a more reliable one? Sometimes a clear ranking of services is possible—for example, if one service wrappers another one and is intended to supersede it, but more often, different services are just…different.

Happily, these differences can be expressed using service properties. You can tie down your choice of service quite tightly by adding very specific service properties to the services and using a service filter in your Blueprint XML. If you wanted to lock things down absolutely you could even use the implementation name as a service property. The risk of this strategy is that if no available service satisfies your filter, you’ll be left with none at all. The more specific your filter, the greater the risk. In general, you should choose service properties that express something about the contract the service satisfies (for example, is.transactional=true) rather than ones that are unique to one particular service (such as vendor=SomeCorp).

Being flexible about which service is used can feel uncomfortable at first. How can you possibly test your code? What happens if the provided service turns out to be terrible? What if it just doesn’t do what you need? Nonetheless, it’s best to leave trust the application deployer and leave the decision about what service gets used to deploy-time, rather than trying to specify everything at development time.

It can be hard to give up control, but being service-oriented and loosely coupled means—well, just that, really!

Services and state

It should be obvious from this discussion that holding much state in services is going to present some problems. An injected service may be transparently swapped for another implementation if the original service goes away or if a higher-ranked service comes along. (More fundamentally, a bundle can be restarted or upgraded at any time, which destroys state.)

If it’s absolutely essential that your services store state, all is not lost. After all, in many realistic application environments the system is by design stable—services won’t be dynamically swapped out or bundles arbitrarily bounced, even though such things are possible in principle.

As a starting point, you’ll almost certainly want to use the singleton scope for beans that represent stateful services, rather than the prototype scope. The singleton scope ensures each user of the service gets its own instance. (Don’t forget that this has scalability implications.)

The singleton scope ensures no service instance is being used by more than one bean, but it doesn’t solve the problem of more than one service instance being used by the same bean. Ultimately, even in a relatively stable system, nothing can stop a service instance going away if an unexpected catastrophe occurs. (Your authors have been imagining a mischievous cat sneaking into the ops center and walking all over the keyboard, disabling parts of the system, but feel free to invent your own entertaining mental pictures!)

If you’re programming defensively, you’ll protect yourself against the disappearance of stateful services, even if such things are extremely unlikely. (The system is stable, maintenance is done in scheduled downtime windows, and you keep a dog in the ops centre.) Nothing can get the stateful service back once it’s gone, but as long as you detect the disappearance you can initiate data recovery procedures or throw an exception to indicate a catastrophic event.
Both the core OSGi programming model and Blueprint allow you to closely monitor the lifecycle of services. Not only does this allow you to be confident your stateful services aren’t disappearing, it has a variety of other uses, particularly for services which are optional.

Monitoring the lifecycle

Closely monitoring what’s going on with the services you care about is a convenient way of handling optional services and service multiplicity.
If a service has been declared optional, how do you know if it’s there and safe to use or not? If you have a reference list, how can you know when things magically get added or removed? If you’re interested, you can register service listeners which are notified when services are registered or unregistered. (If you’re not using Blueprint, you have to care when these things happen. With Blueprint, caring is optional!)

Reference listeners

Reference listeners have a number of potential uses, but we’ll start with a straightforward case, caching. For example, in the Fancy Foods application, the current implementation of the offer aggregator resorts the offer list every time a request is made. Users are likely to hit the offer page far more often than the Fancy Foods business updates its special offers, so caching the sorted list is an obvious performance optimization.

The code changes to use a cache instead of recalculating the sorted list are straightforward:

private void sortOfferList() {
		if (offers != null) {
			sortedOffers = new ArrayList(offers);
			Collections.sort(sortedOffers, new OfferComparator());
			int offerCount = Math.min(MAX_OFFERS, sortedOffers.size());
			sortedOffers = sortedOffers.subList(0, offerCount);
		}
		}
		@Override
		public List getCurrentOffers() {
			return sortedOffers;
	}

The challenge is to know when the cache should be updated, and this is where the reference listeners come in. Registering a reference listener ensures the OfferAggregator bean is notified every time an offer is added to or removed from the list. To register the reference listener, a declaration is added as a child of the or element.

<reference-list id="specialOffers" interface="fancyfoods.offers.SpecialOffer">
<reference-listener ref="offerAggregator" bind-method="bind" unbind-method="unbind" />
	</reference-list>

The reference listener need not be the same as the bean that uses the services. It can even be a completely new bean declared with an inlinedelement, although in that case the bean will always have a singleton scope.

The bind method is only called if the new service is consumed by the bean. In the case of a reference list, this is every time a matching service is added to the service registry. When a reference is used, on the other hand, the consumed service will only be changed (and the bind method called) if the new service has a higher service ranking than the existing one.

The bind method for the offer aggregator is quite simple; when something is added to the list, the cache should be updated. If you’re interested in the service properties, your bind and unbind methods can also be written to take an optional Map which lists all of the service properties.

public void bind(SpecialOffer offer) {
		sortOfferList();
	}

The bind method is called after the new service has been added to the list, so the sorted list will be accurate. The unbind method, on the other hand, is called before the reference is removed. Just calling the method to re-resort the offer list and update the cache in the unbind method is not much use, since it’s the old list that will be sorted. Instead, you’ll need to get your hands a bit dirty and remove the obsolete offer from the cache manually.

public void unbind(SpecialOffer offer) {
		if (sortedOffers != null) {
			sortedOffers.remove(offer);
		}
	}

WARNING One side effect of the proxying of services is that the equals() method on a proxied object may return false, even if == returns true. Until this is fixed, the safest workaround is to make sure to implement equals() on your services.

We’ve got one more little change to make for everything to work. Remember that OSGi is dynamic (we might have mentioned that once or twice already!). Bundle startup order is not deterministic, and so the collection of references may be populated before it’s injected into the OfferAggregator or after. If the list is populated first, then the bind() method will be called when the offers field is still null. We’ve guarded against this in the sortOffersList() method, so there’s no risk of a NullPointerException from bind() method. However, there is a risk the sortedOffers field will never be initialized at all, if all all the bind() calls happen before the injection. Luckily, there is an easy solution, which is to ensure we also sort when the offers are injected:

public void setOffers(List offers) {
		this.offers = offers;//
	sortOfferList();

If you build the updated bundle and drop it into your load, you should find that everything works as it did before. You can test your caching by using the OSGi console to stop and start the chocolate and cheese bundles. If you’ve got things right, your stopped services will continue to appear and disappear appropriately from your web page when you refresh it.

Other uses for monitoring

Caching is a nice use for reference listeners, but it’s not the only one. In the case of our example, when new special offers appear, they could be highlighted or bumped to the top of an otherwise sorted list. They can also be act as a fail-safe when using stateful services, as we discussed earlier. Finally, they allow you to avoid calling a service which has gone away and blocking in the proxy, or to disable parts of your application if a service goes away.

WARNING: DETECTING SERVICE SWAPS

If you’re planning to use reference listeners to detect when an injected service has been swapped out (for example, to reconstitute a stateful service), be careful. If you’re using a reference, the bind-method and unbind-method methods are not symmetric. The bind-method will be called every time a new service is bound—that is, every time the service changes. However, the unbind-method will only be called if a service goes away and cannot immediately be replaced—that is, if your bean is left without a service. Only the bind-method, therefore, can be reliably used to detect when the service has changed, whereas the unbind-method is most useful to detect periods of service unavailability.

Registration listeners

Usually when services appear and disappear, it’s the users of the service which need to be notified. However, the services themselves can be notified of their registration and unregistration using a registration listener. Remember that a service being registered is a necessary prerequisite for a service being bound to a consuming being, but it’s not the same thing. Just because a service is registered doesn’t mean anything at all is using it.

Why would you want to know if a service has been registered and unregistered? Notification of unregistration has an obvious use case for teardown and tidying up. An unregistration listener can serve the same function as a Java finalizer but without the issues of finalizers.

There are fewer obvious uses for service registration listeners, but they can sometimes be useful for initialization. If nothing else, they’re handy for tracing the dynamics of your system!

Summary

The dynamism of OSGi services isn’t without its downside; in their raw form registering them is a bit verbose and accessing them, if done in a safe way, is very verbose indeed. Blueprint neatly eliminates these issues. It makes providing services easy and insulates applications from the dynamism of services. Perhaps most conveniently, Blueprint manages dependency chains so that services (and ordinary Blueprint beans) only become available when all their dependencies are present; if services go away, the services that depend on them are automatically unregistered.

Comments

comments

About Krishna Srinivasan

He is Founder and Chief Editor of JavaBeat. He has more than 8+ years of experience on developing Web applications. He writes about Spring, DOJO, JSF, Hibernate and many other emerging technologies in this blog.

Comments

  1. Renato Athaydes says:

    Best Blueprint OSGi guide on the Internet! Good job!

Speak Your Mind

*