peaberry Stuart McCulloch
Today's menu Origin of peaberry OSGi-fying Guice Service providers Service registry abstraction Blending services & extensions Future ideas
patched Guice to support service “auto-binding” Origin Began as lab project at community site @Inject @OSGiService("(Currency=GBP)") StockQuote quote; new annotations patched Guice to support service “auto-binding” @OSGiService(...) StockQuote quote; // magically added for you bind(StockQuote.class)...
Auto-binding introduced too much magic so... Simplification Auto-binding introduced too much magic so... @Inject @OSGiService("(Currency=GBP)") StockQuote quote; NO new annotations service bindings now explicit, just like pure Guice aim to be a true extension to Guice – no patches!
& Squeezing Guice into a bundle Guice now has OSGi metadata thanks to BND Guice's type-safety meant no major classloading issues ... but AOP proxies initially didn't work in OSGi AOP proxies need to see client types & internal AOP support types
com.google.inject.internal.* Bridge class loader Don't want to expose AOP internals (repackaged CGLIB) A class loader B class loader com.google.inject.internal.* parent parent Guice class loader bridge A bridge B loadClass loadClass proxy proxy proxy proxy proxy so load proxy classes using “bridge” class loaders
Bridge class loader (2) No dependency on OSGi ! – only used when needed weak cache of bridge classloaders allows re-use ... as well as eager unloading of proxy classes BUT cannot apply bridging to package-private types as not visible from other classloaders
:( :) Why peaberry? Guice can now be used in OSGi – so what's missing? no support for dynamic OSGi services! each injector uses immutable set of explicit bindings :( so ... new Injector on every service change? :) or ... Provider<T> that returns dynamic proxies?
Service Provider @Inject StockQuote quote; quote.price(“JAVA”); p r o x y get @Inject StockQuote quote; K P P K P injector K P p r o x y Service Registry get price quote.price(“JAVA”); RealQuote unget
Fluent API helps you get the right service Provider<T> Service binding Fluent API helps you get the right service Provider<T> @Inject A bestService; bind(A.class).toProvider(service(A.class).single()); @Inject Iterable<A> allServices; // each element is a proxy bind(iterable(A.class)).toProvider(service(A.class).multiple()); * import static org.ops4j.peaberry.Peaberry.service; import static org.ops4j.peaberry.util.TypeLiterals.iterable;
Service binding (2) So is that it? s:
So is that it? ... no, you can also Service binding (2) So is that it? ... no, you can also service(A.class).filter(/* apply a filter */)... service(A.class).in(/* query a specific registry */)... service(A.class).out(/* watch for service changes */)... service(A.class).decoratedWith(/* apply decoration */)... each stage of the builder creates an immutable copy which means you can share and re-use builders
Pluggable API – integrate all kinds of service registries Service Registry Pluggable API – integrate all kinds of service registries public interface ServiceRegistry { <T> Iterable<Import<T>> lookup(Class<T> clazz, AttributeFilter filter); <T> void watch(Class<T> clazz, AttributeFilter filter, ServiceWatcher<? super T> watcher); } simple, allows lazinesszzz, no dependency on OSGi service filters not just limited to LDAP strings public interface AttributeFilter { boolean matches(Map<String, ?> attributes); } but we do provide an LDAP adapter (among others)
Tracking service use is very important ! public interface Import<T> { T get(); // may throw unchecked ServiceUnavailableException Map<String, ?> attributes(); void unget(); boolean available(); } can easily apply deco ation to imported services public interface ImportDecorator<S> { <T extends S> Import<T> decorate(Import<T> service); } to change dynamic behaviour (e.g. sticky services)
Single services wrap iterables to look like single imports Concurrent Import Single services wrap iterables to look like single imports public synchronized T get() { count++; if (null == service) { final Iterator<Import<T>> i = services.iterator(); if (i.hasNext()) { service = i.next(); instance = service.get(); } return instance; public synchronized void unget() { if (0 == --count && null != service) { final Import<T> temp = service; instance = null; service = null; temp.unget(); avoids thread-locals, provides basic service affinity
Export What's the opposite of a imported service? hint
Can alter/remove exported instance, update attributes public interface Export<T> { void put(T instance); void attributes(Map<String, ?> attributes); void unput(); } watchers can receive imports and (re-)export them public interface ServiceWatcher<S> { <T extends S> Export<T> add(Import<T> service); } but wait, isn't a registry a bit like a watcher?
Service Registry (revisited) Yes, and exports are also related to imports public interface ServiceRegistry extends ServiceWatcher<Object> { // etc... public interface Export<T> extends Import<T> { // etc... which leads to interesting possibilities ... // like pushing services from one registry to another for (Import<A> i : extensionRegistry.lookup(A.class, null)) { serviceRegistry.add(i); } but you don't have to use the raw API to export
Service binding (revisited) Exported service handles can be injected @Inject Export<A> exportedService; bind(export(A.class)).toProvider(service(aImpl).export()); can defer publishing a service by passing in null bind(export(A.class)).toProvider(service((A)null).export()); otherwise published when export handle is created * import static org.ops4j.peaberry.Peaberry.service; import static org.ops4j.peaberry.util.TypeLiterals.export;
Eclipse extensions Eclipse has its own registry for plug-in extensions this is like another service registry, but less dynamic so ... how can we map the service class to an instance? take inspiration from Eclipse Riena, use bean mapping ! unless its a compatible executable extension OR we're looking up IconfigurationElement class
Same approach as Riena for mapping bean types Mapping extensions Same approach as Riena for mapping bean types But use @ExtensionBean to configure point id @ExtensionBean("examples.menu.items") public interface Item { String getLabel(); @MapName("label") String toString(); @MapContent String getContent(); } Use @MapName, @MapContent from peaberry or Riena
GuiceExtensionFactory, similar approach as Spring Injecting extensions GuiceExtensionFactory, similar approach as Spring <extension point="org.eclipse.ui.views"> <view name="Message" allowMultiple="true" icon="icons/sample2.gif" class="example.ViewImpl" id="example.view" /> </extension> becomes <extension point="org.eclipse.ui.views"> <view name="Message" allowMultiple="true" icon="icons/sample2.gif" class="org.ops4j.peaberry.eclipse.GuiceExtensionFactory:example.ViewImpl" id="example.view" /> </extension> <extension point="org.ops4j.peaberry.eclipse.modules"> <module class="example.ViewModule" />
Blending services and extensions peaberry 1.1-rc2 lets you combine registries binder.install(osgiModule(context, eclipseRegistry())); service proxies will then query both OSGi and Clients can continue to use the same injection points @Inject StockQuote quote; YOU can choose where the service comes from or even whether to use a dynamic service at all !
coming soon: lifecycles, configuration support Summary Guice can now be used in OSGi peaberry adds dynamic services to Guice model easy to switch between services and non-services can mix'n'match different service registries ... like OSGi services and Eclipse extensions coming soon: lifecycles, configuration support
Questions?