OSGi The Service Layer The Whiteboard Pattern Reading
OSGi: The Service Layer. The Whiteboard Pattern
Reading • Ch. 1: OSGi revealed • Ch 2: Mastering modularity • Ch 3: Learning lifecycle • Ch 4: Studying services • Ch 11: Component models and frameworks
What is a service ? • Service = “work done for another” • 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.
OSGi Services • The OSGi framework has a centralized service registry that follows a publish-find-bind model – A providing bundle can publish Plain Old Java Objects (POJOs) as services. – A consuming bundle can find and then bind to services.
OSGi Services are 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, etc • The consumer should be prepared to cope with services coming and going over time.
Accessing Service Registry through the Bundle. Context public interface Bundle. Context {. . . Service. Registration register. Service( String[] clazzes, Object service, Dictionary properties); Service. Registration register. Service( String clazz, Object service, Dictionary properties); Service. Reference[] get. Service. References(String clazz, String filter) throws Invalid. Syntax. Exception; Service. Reference[] get. All. Service. References(String clazz, String filter throws Invalid. Syntax. Exception; Service. Reference get. Service. Reference(String clazz); Object get. Service(Service. Reference reference); boolean unget. Service(Service. Reference reference); void add. Service. Listener(Service. Listener listener, String filter) throws Invalid. Syntax. Exception; void add. Service. Listener(Service. Listener listener); void remove. Service. Listener(Service. Listener listener); . . . }
Accessing Service Registry through the Bundle. Context public interface Bundle. Context {. . . Service. Registration register. Service( methods for String[] clazzes, Object service, Dictionary properties); providers Service. Registration register. Service( String clazz, Object service, Dictionary properties); Service. Reference[] get. Service. References(String clazz, String filter) throws Invalid. Syntax. Exception; Service. Reference[] get. All. Service. References(String clazz, String filter methods for throws Invalid. Syntax. Exception; Service. Reference get. Service. Reference(String clazz); consumers Object get. Service(Service. Reference reference); boolean unget. Service(Service. Reference reference); void add. Service. Listener(Service. Listener listener, String filter) throws Invalid. Syntax. Exception; service events void add. Service. Listener(Service. Listener listener); void remove. Service. Listener(Service. Listener listener); . . . }
Basics of a Service Publisher (1) • To publish a service in OSGi, you need to provide following information to the OSGi service registry: – an array of interface names (the names of the interfaces implemented by the service) – the service implementation (an object – instance of a class implementing the interfaces described above. It is a POJO object – it does not need to implement any OSGi Framework specific interfaces) – optional a dictionary of metadata (the metadata are name-value pairs; you may register any custom attribute names)
Basics of a Service Publisher (2) public interface Bundle. Context {. . . Service. Registration register. Service( String[] clazzes, Object service, Dictionary properties); Service. Registration register. Service( String clazz, Object service, Dictionary properties); Service. Reference[] get. Service. References(String clazz, String filter) throws Invalid. Syntax. Exception; Service. Reference[] get. All. Service. References(String clazz, String filter throws Invalid. Syntax. Exception; Service. Reference get. Service. Reference(String clazz); Object get. Service(Service. Reference reference); boolean unget. Service(Service. Reference reference); void add. Service. Listener(Service. Listener listener, String filter) throws Invalid. Syntax. Exception; void add. Service. Listener(Service. Listener listener); void remove. Service. Listener(Service. Listener listener); . . . }
Basics of a Service Publisher (3) • The service is published by using the bundle context: • Service. Registration registration = bundle. Context. register. Service(interfaces, serverobj, metadata); • The registry returns a Service. Registration object for the published service, which you can use to update later the service metadata or to remove the service from the registry. • The metadata of a service can be changed later at any time by using its service registration: • registration. set. Properties(new. Metadata);
Basics of a Service Publisher (4) • The publishing bundle can also remove a published service at any time: • registration. unregister(); • If the bundle stops before all published services are removed, the framework keeps track of what was registered, and any services that haven’t yet been removed when a bundle stops are automatically removed by the framework.
Service Publisher Examples • Bundles that publish different Greeting services. • Interface = Greeting, located in bundle org. foo. hello • Implementation classes: Formal. Greeting. Impl, Friendly. Greeting. Impl, Custom. Greeting. Impl • Each implementation is located in a bundle: org. foo. hello. formalgreeting, org. foo. hello. friendlygreeting, org. foo. hello. customgreeting • Each of these bundles has an activator that instantiates Greeting objects and registers them as OSGi services • When services are registered with the OSGi service registry, they get a value for a property “type”, that may have the values “formal” or “friendly” • Each bundle imports the org. foo. hello package (containing the Greeting interface) but the bundles do not export any packages; they just provide the registered services
org. foo. hello Greeting org. foo. hello. formalg Greeting. Impl org. foo. hello. friendlyg Greeting. Impl Activator Greeting, type=formal Greeting, type=friendly org. foo. hello. customg Greeting. Impl Activator Greeting, type=formal type=friendly
Service Publisher Example: formalgreeting Implementation package org. foo. hello. formalgreeting; import org. foo. hello. Greeting; public class Greeting. Impl implements Greeting { public String say. Hello() { return "Good day, Sir !"; } }
Service Publisher Example: formalgreeting Activator package org. foo. hello. formalgreeting; import java. util. Dictionary; import java. util. Properties; import org. foo. hello. Greeting; import org. osgi. framework. Bundle. Activator; import org. osgi. framework. Bundle. Context; import org. osgi. framework. Service. Registration; public class Activator implements Bundle. Activator { public void start(Bundle. Context context) throws Exception { String[] interfaces = new String[] { Greeting. class. get. Name() }; Dictionary metadata = new Properties(); ((Properties) metadata). set. Property("type", "formal"); Greeting formal. Greeting = new Greeting. Impl(); Service. Registration registration 1 = context. register. Service(interfaces, formal. Greeting, metadata); System. out. println("formalgreeting registered a Greeting Service"); }
Basics of a Service Consumer (1) • The consumer need to give details from the service contract to discover the right services in the registry. • The simplest query takes a single interface name: • Service. Reference reference = bundle. Context. get. Service. Reference(Greeting. class. get. Name()); • The registry returns a service reference, which is an indirect reference to the discovered service. – But why does the registry return an indirect reference and not the actual service implementation? – 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
Basics of a Service Consumer (2) public interface Bundle. Context {. . . Service. Registration register. Service( String[] clazzes, Object service, Dictionary properties); Service. Registration register. Service( String clazz, Object service, Dictionary properties); Service. Reference[] get. Service. References(String clazz, String filter) throws Invalid. Syntax. Exception; Service. Reference[] get. All. Service. References(String clazz, String filter throws Invalid. Syntax. Exception; Service. Reference get. Service. Reference(String clazz); Object get. Service(Service. Reference reference); boolean unget. Service(Service. Reference reference); void add. Service. Listener(Service. Listener listener, String filter) throws Invalid. Syntax. Exception; void add. Service. Listener(Service. Listener listener); void remove. Service. Listener(Service. Listener listener); . . . }
Basics of a Service Consumer (3) • Before you can use a service, you must bind to the actual implementation from the registry: • Greeting gr = (Greeting) bundle. Context. get. Service(reference); • The implementation returned is typically the same POJO instance previously registered with the registry, although the OSGi specification doesn’t prohibit the use of proxies or wrappers • Each time you call get. Service(), the registry increments a usage count so it can keep track of who is using a particular service. When you’ve finished with a service, you should tell the registry: • bundle. Context. unget. Service(reference); • gr = null;
Basics of a Service Consumer (4) • if you want to discover services with certain properties, you must use another query method that accepts a standard LDAP filter string, and returns one/all services matching the filter. • Service. Reference[] references = bundle. Context. get. Service. References(Stock. Listin, class. get. Name(), "(currency=GBP)"); • Service. Reference[] references = bundle. Context. get. Service. References(Stock. Listing. class. get. Name(), "(&(currency=GBP)(object. Class=org. example. Stock. Chart))");
Quick guide to 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))
Service Consumer Example org. foo. hello Greeting org. foo. hello. basicconsumer Activator Greeting, type=formal
Service Consumer Example: basicconsumer Activator package org. foo. hello. basicconsumer; import org. foo. hello. Greeting; … public class Activator implements Bundle. Activator { public void start(Bundle. Context context) throws Exception { System. out. println("Basic. Consumer - Hello World!!"); Service. Reference[] references = context. get. Service. References( Greeting. class. get. Name(), "(type=formal)"); if (references==null) { System. out. println("No matching services found in the registry"); return; } Greeting g; for (Service. Reference r : references) { g= (Greeting)(context. get. Service(r)); if (g!=null) System. out. println(g. say. Hello()); } } …
Dealing with dynamics • The first part covered the basics of OSGi services, showing the basics of how to publish and discover services. • In this section, we’ll look more closely at the dynamics of services: – Techniques to help you write robust OSGi applications – Techniques to handle the dynamics of OSGi services with the least amount of effort: • Service. Listener • Service. Tracker
Good practices for dealing with dynamics • Do not store the service instance in a field for later use ! Otherwise the consumer won’t know when the service is retracted by its providing bundle and get a runtime error. • Storing the indirect service reference instead of the service instance is better. You request the service instance from the reference just before needing it, and if the service is not available any more, you get a null object (just have to test it) • The best is to always look up the service just before you want to use it, not get the service reference only at startup: in this way you may capture services appeared in the meantime • Take into account that a service can disappear at any moment – including that short time between the calls to get. Service. Reference() and get. Service() – add more tests and try-catch for robust code !
Listening for Services • For services, OSGi Framework has 3 types of 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.
The Service. Listener Interface public interface Service. Listener extends Event. Listener { public void service. Changed(Service. Event event); } • Every service listener must implement this interface in order to receive service events • Using a Service. Listener avoids the cost of repeatedly looking up the service by caching it on its REGISTERED event and dealing with its disappearing on the UNREGISTERING event
Managing Service. Listeners public interface Bundle. Context {. . . Service. Registration register. Service( String[] clazzes, Object service, Dictionary properties); Service. Registration register. Service( String clazz, Object service, Dictionary properties); Service. Reference[] get. Service. References(String clazz, String filter) throws Invalid. Syntax. Exception; Service. Reference[] get. All. Service. References(String clazz, String filter throws Invalid. Syntax. Exception; Service. Reference get. Service. Reference(String clazz); Object get. Service(Service. Reference reference); boolean unget. Service(Service. Reference reference); void add. Service. Listener(Service. Listener listener, String filter) throws Invalid. Syntax. Exception; void add. Service. Listener(Service. Listener listener); void remove. Service. Listener(Service. Listener listener); . . . }
Example: Service Consumer with Service. Listener • If a service consumer wants to keep track with the dynamic appearance and disappearence of registered services, it may use Service. Listeners instead of constantly polling the service. Registry • The limitation of Service. Listener is that it cannot be used to retrieve services that were already existing when the service listener was registered (their REGISTERED type of event happened before the registration of the listener)
Tracking for Services • A Service. Tracker can be used to retrieve all the available services at a moment • Provides a safe way for you to get the benefits of service listeners without the pain • Constructing a Service. Tracker: constructor takes a bundle context, the service type you want to track, a filter, and optionally a customizer object (may be null). • 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. • You can get the tracked service(s) by using the methods get. Service() and get. Services() • When you are finished with the tracker, you should call close() so that all the tracked resources can be properly cleared
Example: Service Consumer with Service. Tracker (1) package org. foo. hello. trackerconsumer; import import org. osgi. framework. Bundle. Activator; org. osgi. framework. Bundle. Context; org. osgi. framework. Filter; org. osgi. framework. Invalid. Syntax. Exception; org. osgi. util. tracker. Service. Tracker; org. foo. hello. Greeting; public class Activator implements Bundle. Activator { Bundle. Context m_context; Service. Tracker m_greet. Tracker; My. Thread m_t;
Example: Service Consumer with Service. Tracker (2) public void start(Bundle. Context context) { m_context = context; String filter. String = "(&(object. Class=org. foo. hello. Greeting)(type=formal))"; Filter filter = null; try { filter = context. create. Filter(filter. String); } catch (Invalid. Syntax. Exception e) { System. out. println("Invalid synytax in Filter"); e. print. Stack. Trace(); } m_greet. Tracker = new Service. Tracker(context, filter, null); m_greet. Tracker. open(); m_t = new My. Thread(); m_t. start(); } public void stop(Bundle. Context context) { m_t. end(); m_greet. Tracker. close(); }
Example: Service Consumer with Service. Tracker (3) class My. Thread extends Thread { private volatile boolean active = true; public void end() { active = false; } public void run() { while (active) { Object greetings[] = m_greet. Tracker. get. Services(); if (greetings == null) { System. out. println("Tracker. Consumer: No matching Greeting. . . "); } else { for (Object g : greetings) System. out. print(((Greeting) g). say. Hello() + " "); System. out. println(); } try { Thread. sleep(5000); } catch (Exception e) { System. out. println("Thread interrupted " + e. get. Message()); } } }
Tracking for Services - Events • A Service. Tracker can be used to retrieve all the available services at a moment • In order to be get also event notifications at the moments of a service registering or unregistering (in order to do additional operations) you have to provide a Service. Tracker. Customizer object when constructing the Service. Tracker
Service. Tracker. Customizer public interface Service. Tracker. Customizer { public Object adding. Service(Service. Reference reference); public void modified. Service(Service. Reference reference, Object service); public void removed. Service(Service. Reference reference, Object service); } • You may extend a service tracker with a customizer object. The Service. Tracker. Customizer interface provides a safe way to enhance a tracker by intercepting tracked service instances • Like a service listener, a customizer is based on the three major events in the life of a service: adding, modifying, and removing.
Summary on accessing OSGi services • Three different ways to access OSGi services: – directly through the bundle context – reactively with service listeners – 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. – 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.
Service-Based Dynamic Extensibility: The Whiteboard Pattern • Service events provide a mechanism for dynamic extensibility • The whiteboard pattern – Treats the service registry as a whiteboard – An application component listens for services of a particular type to be added and removed: – On addition, the service is integrated into the application – On removal, the service is removed from the application
The Whiteboard Pattern Framework Service Registry 3. Event 1. Register event listener notification ADD 6. Event notification REMOVE 4. Use cached service(s) 2. Publish service 5. Unregister service
Service-based Dynamic Extensibility: Example: The Greeting Application Greeting Trackerconsumer Gr. Impl 1 Gr. Impl 2
Relating services 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 Class. Cast. Exception. • If you want to query all services, regardless of what interfaces you can see, even if they aren’t compatible with your bundle, regardless of whether their interfaces are visible to the calling bundle: get. All. Service. References() • Service. Reference[] references = bundle. Context. get. All. Service. References(null, null);
Bundle-specific Services. Service Factories • Sometimes you want to create services lazily or customize a service specifically for each bundle using it. • The OSGi framework defines a special interface to use when registering a service. The Service. Factory 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 • The framework caches factory-created service instances, so a bundle requesting the same service twice receives the same instance.
Standard OSGi Services • Standard services are used throughout the OSGi specification to extend the framework, but keeping the core API small and clean • Types of standard services: – Core services: generally implemented by the OSGi framework itself, because they’re intimately tied to framework internals – Compendium Services: in addition to the core services, the OSGi Alliance defines a set of non-core standard services called the compendium services. These services are provided as separate bundles by framework implementers or other third parties and typically work on all frameworks.
Standard OSGi Services
Summary • A service is “work done for another. ” • Service contracts define responsibilities and match consumers with providers. • 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 Service. Tracker utility class. • Services can be used to create dynamically extensible applications with the whiteboard pattern. • For higher-level service abstractions, consider the more advanced component models over OSGi
- Slides: 43