Motivation for Component Frameworks

Download Report

Transcript Motivation for Component Frameworks

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 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(
methods for
String[] clazzes, Object service, Dictionary properties);
providers
ServiceRegistration registerService(
String clazz, Object service, Dictionary properties);
ServiceReference[] getServiceReferences(String clazz, String filter)
throws InvalidSyntaxException;
ServiceReference[] getAllServiceReferences(String clazz, String filter
methods for
throws InvalidSyntaxException;
ServiceReference getServiceReference(String clazz);consumers
Object getService(ServiceReference reference);
boolean ungetService(ServiceReference reference);
void addServiceListener(ServiceListener listener, String filter)
throws InvalidSyntaxException;
service events
void addServiceListener(ServiceListener listener);
void removeServiceListener(ServiceListener 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 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
GreetingImpl
org.foo.hello.friendlyg
GreetingImpl
Activator
Activator
Greeting,
type=formal
Greeting,
type=friendly
org.foo.hello.customg
GreetingImpl
Activator
Greeting,
Greeting,
type=formal type=friendly
Service Publisher Example:
formalgreeting Implementation
package org.foo.hello.formalgreeting;
import org.foo.hello.Greeting;
public class 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:
–
•
Perform wildcard matching:
–
•
(&(name=John Smith)(occupation=doctor))
Match at least one of the contained clauses:
–
•
(name=*)
Match all the contained clauses:
–
•
(name=Jo*n*Smith*)
Determine if an attribute exists:
–
•
(name~=johnsmith)
(|(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
public interface ServiceListener extends EventListener {
public void serviceChanged(ServiceEvent event);
}
• Every service listener must implement this interface in
order to receive service events
• Using a ServiceListener 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 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
import
import
import
import
import
org.osgi.framework.BundleActivator;
org.osgi.framework.BundleContext;
org.osgi.framework.Filter;
org.osgi.framework.InvalidSyntaxException;
org.osgi.util.tracker.ServiceTracker;
org.foo.hello.Greeting;
public class Activator implements BundleActivator {
BundleContext m_context;
ServiceTracker m_greetTracker;
MyThread m_t;
Example: Service Consumer with
ServiceTracker (2)
public void 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();
}
public void stop(BundleContext context) {
m_t.end();
m_greetTracker.close();
}
Example: Service Consumer with
ServiceTracker (3)
class MyThread extends Thread {
private volatile boolean active = true;
public void end() {
active = false;
}
public void 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
public interface ServiceTrackerCustomizer {
public Object addingService(ServiceReference reference);
public void modifiedService(ServiceReference reference,
Object service);
public void removedService(ServiceReference reference,
Object service);
}
• 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.
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)
5. Unregister
service
2. Publish
service
Service-based Dynamic Extensibility:
Example: The Greeting Application
Greeting
Trackerconsumer
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.
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
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