430 likes | 448 Views
Dive into the world of OSGi services with insights on the Whiteboard Pattern, dynamic service handling, and accessing the service registry through BundleContext. Learn how to publish, find, bind, and manage services effectively in an OSGi environment.
E N D
Ch.1: OSGi revealed Ch 2: Mastering modularity Ch 3: Learning lifecycle Ch 4: Studying services Ch 11: Component models and frameworks Reading
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 BundleContext public interface BundleContext { ... 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); void addServiceListener(ServiceListener listener, String filter) throws InvalidSyntaxException; void addServiceListener(ServiceListener listener); void removeServiceListener(ServiceListener listener); ... }
Accessing Service Registry through the BundleContext public interface BundleContext { ... 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); void addServiceListener(ServiceListener listener, String filter) throws InvalidSyntaxException; void addServiceListener(ServiceListener listener); void removeServiceListener(ServiceListener listener); ... } methods for providers methods for consumers service events
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 BundleContext { ... 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); void addServiceListener(ServiceListener listener, String filter) throws InvalidSyntaxException; void addServiceListener(ServiceListener listener); void removeServiceListener(ServiceListener listener); ... }
Basics of a Service Publisher (3) • The service is published by using the bundle context: • ServiceRegistration registration = bundleContext.registerService(interfaces, serverobj, metadata); • The registry returns a ServiceRegistration 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.setProperties(newMetadata);
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: FormalGreetingImpl, FriendlyGreetingImpl, CustomGreetingImpl • 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 org.foo.hello.friendlyg org.foo.hello.customg GreetingImpl GreetingImpl GreetingImpl Activator Activator Activator Greeting, type=friendly Greeting, type=friendly Greeting, type=formal Greeting, type=formal
Service Publisher Example:formalgreeting Implementation package org.foo.hello.formalgreeting; import org.foo.hello.Greeting; publicclass GreetingImpl implements Greeting { public String sayHello() { 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.BundleActivator; import org.osgi.framework.BundleContext; import org.osgi.framework.ServiceRegistration; public class Activator implements BundleActivator { public void start(BundleContext context) throws Exception { String[] interfaces = new String[] { Greeting.class.getName() }; Dictionary metadata = new Properties(); ((Properties) metadata).setProperty("type", "formal"); Greeting formalGreeting = new GreetingImpl(); ServiceRegistration registration1 = context.registerService(interfaces, formalGreeting, 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: • ServiceReference reference = bundleContext.getServiceReference(Greeting.class.getName()); • 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 BundleContext { ... 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); void addServiceListener(ServiceListener listener, String filter) throws InvalidSyntaxException; void addServiceListener(ServiceListener listener); void removeServiceListener(ServiceListener 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) bundleContext.getService(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 getService(), 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: • bundleContext.ungetService(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. • ServiceReference[] references = bundleContext.getServiceReferences(StockListin, class.getName(), "(currency=GBP)"); • ServiceReference[] references = bundleContext.getServiceReferences(StockListing.class.getName(), "(&(currency=GBP)(objectClass=org.example.StockChart))");
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 BundleActivator { public void start(BundleContext context) throws Exception { System.out.println("BasicConsumer - Hello World!!"); ServiceReference[] references = context.getServiceReferences( Greeting.class.getName(), "(type=formal)"); if (references==null) { System.out.println("No matching services found in the registry"); return; } Greeting g; for (ServiceReference r : references) { g= (Greeting)(context.getService(r)); if (g!=null) System.out.println(g.sayHello()); } } …
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: • ServiceListener • ServiceTracker
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 getServiceReference() and getService() – 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 ServiceListener Interface • Every service listener must implement this interface in order to receive service events • Using a ServiceListeneravoids the cost of repeatedly looking up the service by caching it on its REGISTERED event and dealing with its disappearing on the UNREGISTERING event public interface ServiceListener extends EventListener { public void serviceChanged(ServiceEvent event); }
Managing ServiceListeners public interface BundleContext { ... 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); void addServiceListener(ServiceListener listener, String filter) throws InvalidSyntaxException; void addServiceListener(ServiceListener listener); void removeServiceListener(ServiceListener listener); ... }
Example: Service Consumer with ServiceListener • If a service consumer wants to keep track with the dynamic appearance and disappearence of registered services, it may use ServiceListeners instead of constantly polling the serviceRegistry • The limitation of ServiceListener 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 ServiceTracker 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 ServiceTracker: 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 getService() and getServices() • 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 ServiceTracker (1) package org.foo.hello.trackerconsumer; import org.osgi.framework.BundleActivator; import org.osgi.framework.BundleContext; import org.osgi.framework.Filter; import org.osgi.framework.InvalidSyntaxException; import org.osgi.util.tracker.ServiceTracker; import org.foo.hello.Greeting; publicclass Activator implements BundleActivator { BundleContext m_context; ServiceTracker m_greetTracker; MyThread m_t;
Example: Service Consumer with ServiceTracker (2) publicvoid start(BundleContext context) { m_context = context; String filterString = "(&(objectClass=org.foo.hello.Greeting)(type=formal))"; Filter filter = null; try { filter = context.createFilter(filterString); } catch (InvalidSyntaxException e) { System.out.println("Invalid synytax in Filter"); e.printStackTrace(); } m_greetTracker = new ServiceTracker(context, filter, null); m_greetTracker.open(); m_t = new MyThread(); m_t.start(); } publicvoid stop(BundleContext context) { m_t.end(); m_greetTracker.close(); }
Example: Service Consumer with ServiceTracker (3) class MyThread extends Thread { privatevolatileboolean active = true; publicvoid end() { active = false; } publicvoid run() { while (active) { Object greetings[] = m_greetTracker.getServices(); if (greetings == null) { System.out.println("TrackerConsumer: No matching Greeting ..."); } else { for (Object g : greetings) System.out.print(((Greeting) g).sayHello() + " "); System.out.println(); } try { Thread.sleep(5000); } catch (Exception e) { System.out.println("Thread interrupted " + e.getMessage()); } } } } }
Tracking for Services - Events • A ServiceTracker 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 ServiceTrackerCustomizer object when constructing the ServiceTracker
ServiceTrackerCustomizer • You may extend a service tracker with a customizer object. The ServiceTrackerCustomizer 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. public interface ServiceTrackerCustomizer { public Object addingService(ServiceReference reference); public void modifiedService(ServiceReference reference, Object service); public void removedService(ServiceReference reference, Object service); }
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 notification - ADD • Register event listener 5. Unregister service 6. Event notification - REMOVE 2. Publish service 4. Use cached service(s)
Service-based Dynamic Extensibility:Example: The Greeting Application Trackerconsumer Greeting GrImpl1 GrImpl2
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 ClassCastException. • 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: getAllServiceReferences() • ServiceReference[] references = bundleContext.getAllServiceReferences(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 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 • 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.
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 ServiceTracker 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