[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index] [List Home]
Re: [ecf-dev] Fun with remote services part 1

Hi Scott, 

thank you for such a detailed answer. Unfortunately after tree days of trying I realized that for my case (working with XMPP) ECF and DS won't work. But you saved me from spending another week on this problem!

Thanks,
Eugen 

Am Jun 14, 2010 um 20:08  schrieb Scott Lewis:

> Hi Eugen,
> 
> Eugen Reiswich wrote:
>> Hi Scott,
>> 
>> thanks for explaining the issues regarding OSGi and credentials for authentication.
>> 
>>> 1) An explicit connect...i.e. the code that you use above to create and connect a container. 2) Implementing and registering your own IProxyContainerFinder, so that upon discovery (and container creation, and connect), that your credentials can be provided.
>> Say I will choose solution #1. 
>> Once I've created an IContainer instance, how would I now tell my services that they can be used remotely. I've read that I need to register the IContainer instance as a service in the host side OSGi service registry?
> 
> Yes.  The OSGi remote services spec specifies that a remote service is one that is registered with values for the following standard-specified service properties:
> 
> service.exported.interfaces
> service.exported.configs
> 
> So, a ds component definition like this is detected by ECF remote services implementation as a remote service (because of the presence of service.exported.interfaces property):
> 
> <scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0"; enabled="true" immediate="true" name="org.eclipse.ecf.examples.remoteservices.hello.ds.host">
>  <implementation class="org.eclipse.ecf.examples.internal.remoteservices.hello.ds.host.HelloComponent"/>
>  <property name="service.exported.interfaces" type="String" value="*"/>
>  <property name="service.exported.configs" type="String" value="ecf.generic.server"/>
>  <property name="org.eclipse.ecf.containerFactoryArgs" type="String" value="ecftcp://localhost:30001/server"/>
>  <service>
>     <provide interface="org.eclipse.ecf.examples.remoteservices.hello.IHello"/>
>  </service>
> 
> Incidently, the above is from the org.eclipse.ecf.examples.remoteservices.hello.ds.host/OSGI-INF/hello.xml example. 
> What happens is that when this service is registered (by ds), the OSGi remote services implementation
> 
> (on *host*)
> 
> a) detects the presence of service.exported.interfaces and service.exported.configs
> class impl:  org.eclipse.ecf.internal.osgi.services.distribution.EventHookImpl
> b) in the ECF impl calls the IHostContainerFinder.findHostContainer to find a host container(s) that can/should export the given remote service. class impl:  org.eclipse.ecf.osgi.services.distribution.DefaultHostContainerFinder
> c) If findHostContainers returns a non-null value (i.e. there are some host containers that should export this service) then the ECF implementation calls remoteServiceContainerAdapter.registerRemoteService(...) for you automatically.  It *also* then calls IDiscoveryAdvertiser.registerService(...) if there is an IDiscoveryAdvertiser present.  Which discovery is used is dependent upon which is configured and deployed to be used (e.g. zeroconf, slp, apache zookeeper, xml-file-based discovery, etc).
> class impl:  org.eclipse.ecf.internal.osgi.services.distribution.EventHookImpl
> 
>> The IContainer implementation will then listen for service registrations containing e.g. the following  properties and publish my service: 
>>   <property name="service.exported.interfaces" type="String" value="*"/>
>>   <property name="service.exported.configs" type="String" value="ecf.xmpp.smack"/>
>>   <property name="org.eclipse.ecf.containerFactoryArgs" type="String" value="jabber-server.de <http://-server.de>"/>
>> 
>> Is this the way it works?
> 
> On the consumer side, the OSGi remote service detection is dependent upon discovering the remote service via an IServiceListener.  The ECF impl of OSGi remote services registers an IServiceListener with the installed/configured IDiscoveryLocator.   When the discovery service (on the consumer) detects a service, it calls the IServiceListener.serviceDiscovered(...), and this triggers the ECF impl of OSGi remote services.  In turn, this calls the IProxyContainerFinder.findProxyContainers to find (or create) the appropriate/relevant containers...and then call containerAdapter.getRemoteServiceReferences(...), create the client's proxy, and then register that proxy in the consumers *local* OSGi service registry.  Once that's done (registering the proxy), any clients that should be notified (e.g. via ServiceTracker or ds) are then notified about the existence of the new remote service. 
> So under normal circumstances, it should not be necessary for you to deal with  the ECF IRemoteServiceContainerAdapter API at all.  On the host side, this is done automatically on any/all containers returned from IHostContainerFinder.findHostContainers, and on the consumer side this is done automatically on any/all containers returned from IProxyContainerFinder.findProxyContainers.
> 
> If, however, you wish to use the IRemoteServiceContainerAdapter API directly (in your own code) then you are completely free to do this...and in such a case you don't need the OSGi remote services implementation at all.  Rather, you can/could implement the calls (as you outline below) to directly create containers, get the IRemoteServiceContainerAdapter and make calls on that the IRemoteServiceContainerAdapter (e.g. registerRemoteService...on host...or getRemoteServiceReferences...on consumer) directly/explicitly yourself.  Like I say above...if you do this (call the IRemoteServiceContainerAdapter on host and client directly), then you do not need the OSGi remote services impl at all.
> 
> To summarize, the layring of the apis is as described by this diagram:
> 
> http://wiki.eclipse.org/OSGi_4.2_Remote_Services_and_ECF
> 
> Now, as for the use of DS...ds is essentially a declarative way to register *OSGI* services (host), and reference/bind to OSGI services (client).  *With* the ECF OSGi remote services implementation, this makes it possible to declaratively register remote services...e.g. here's the org.eclipse.ecf.examples.remoteservices.host.ds/OSGI-INF/hello.xml:
> 
> <scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0"; enabled="true" immediate="true" name="org.eclipse.ecf.examples.remoteservices.hello.ds.host">
>  <implementation class="org.eclipse.ecf.examples.internal.remoteservices.hello.ds.host.HelloComponent"/>
>  <property name="service.exported.interfaces" type="String" value="*"/>
>  <property name="service.exported.configs" type="String" value="ecf.generic.server"/>
>  <property name="org.eclipse.ecf.containerFactoryArgs" type="String" value="ecftcp://localhost:30001/server"/>
>  <service>
>     <provide interface="org.eclipse.ecf.examples.remoteservices.hello.IHello"/>
>  </service>
>  <reference cardinality="1..1" interface="org.eclipse.ecf.core.IContainerFactory" name="IContainerFactory" policy="static"/>
> </scr:component>
> 
> The service properties are then used as described above to a) trigger the ECF remote services service registry eventhook; b) Find/create the appropriate container (via the IHostContainerFinder), and c) register the remote service with both the IRemoteServiceContainerAdapter.registerRemoteService and IDiscoveryAdvertiser.registerService.
> 
> Using DS, on the consumer side is this:
> 
> <scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0"; enabled="true" immediate="true" name="org.eclipse.ecf.examples.remoteservices.hello.ds.consumer">
>  <implementation class="org.eclipse.ecf.examples.internal.remoteservices.hello.ds.consumer.HelloClientComponent"/>
>  <reference bind="bindHello" cardinality="0..n" interface="org.eclipse.ecf.examples.remoteservices.hello.IHello" name="IHello" policy="dynamic"/>
> </scr:component>
> 
> All this says is that when an implementer of the IHello service is *added to the local OSGi registry*, ds will call the HelloClientComponent.bindHello(proxy) method and provide the service reference to the component code.
> 
> As described above, on the consumer, the IHello service instance (actually a proxy) will be registered locally (and trigger ds to do the binding/call bindHello method) when the following have occurred:
> 
> a) The IServiceListener detects a service (using some discovery mechanism)
> class impl:  org.eclipse.ecf.internal.osgi.services.distribution.DiscoveredServiceTrackerImpl
> b) The IProxyContainerFinder finds (or creates) a 'compatible' local/consumer container instance
> class impl:  org.eclipse.ecf.osgi.services.distribution.DefaultProxyContainerFinder
> c) The proxy is created and registered in the consumer's local OSGi service registry.
> class impl:  org.eclipse.ecf.internal.osgi.services.distribution.DiscoveredServiceTrackerImpl
> 
> Note that b depends upon a (i.e. there has to be a local container that is of appropriate config type...either before the discovery or created as part of discovery), and c depends upon b (i.e. the container has to be able to lookup/find a remote service on the container(s) returned from findProxyContainerFinder).
> 
> I think the thing to make immediately clear about the use of DS is that it is based upon services being registered in the *OSGI* service registry.  So on the host side that means that the appropriate standard service properties have to be provided (service.exported.interfaces and service.exported.configs) for the ECF (or any other) remote services impl to 'kick in' and register the remote service (call IRemoteServiceContainerAdapter.registerRemoteService and IDiscoveryAdvertiser.registerService(...)).  On the consumer side it means that the service has to be *discovered* (via some discovery protocol or impl...i.e. xml file-based discovery, zookeeper, slp, zeroconf, your private impl)...so that the ECF remote services impl can find the container(s), lookup and create the reference, and then register the proxy in the consumer's local OSGi registry...and thereby trigger DS references, ServiceTrackers.  Note that DS doesn't really know or care that the service is remote...it's just doing what it's designed to do, and in the case of the remote service the actual service impl is a proxy rather than a pojo.
> 
> The XMPP provider adds some further complexity to this model because (as we discussed):
> 
> 1) There is no way specified in OSGi 4.2 remote services spec to pass in credentials information for the establishment of the connection...so this has to be done using some ECF mechanism (either directly with the container.connect, or via a custom IHost/IProxyContainerFinder.
> 2) XMPP doesn't have any notion of scope for messaging (essentially a flat messaging namespace)...meaning that it's necessary for the host to provide  targetIDs for the registration...i.e. props.put(Constants.SERVICE_REGISTRATION_TARGETS, targetIDs).  In the ECF remote services impl any service properties that are not OSGi standard service properties are simply passed through to the call to IRemoteServiceContainerAdapter.registerRemoteService(...,props), meaning that if you call
> 
> Properties props = new Properties();
> props.put("service.exported.interfaces","*");  <- This says it's a remote service, and will trigger the ECF OSGI 4.2 remote services impl (org.eclipse.ecf.osgi.
> 
> props.put("service.exported.configs","ecf.xmpps.smack");  <- This tells the IHostContainerFinder to find a container with type ecf.xmpps.smack to export
> 
> props.put(Constants.SERVICE_REGISTRATION_TARGETS,targetIDs);  <- upon call to registerRemoteService, this indicates to the ECF XMPP provider that the service registration should be sent to the given targets (since otherwise the xmpp provider has no context to specify who it should send the add registration message to).
> // Host register call...which triggers host-side sequence defined above
> bundleContext.registerService("fooSvcInterface",new HostImpl(), props);
> 
> As for your question about the use of ds and configuration admin to set service properties (rather than setting them via DS xml).  I believe the answer is 'yes' (i.e. you can use configuration admin to set service property values prior to their usage for ds-based service registration), but I'm not sure exactly how that is done most easily. 
> Though the answer to this question is strictly between configuration admin and declarative services and doesn't really involve OSGi remote services, or ECF's implementation of remote services.  If you identify a simple pattern for doing that (setting service property values prior to DS-based service registration) it would be good (I think) to make it known to many, as it seems that many wish to do this.  It seems to me, though, that if you are setting service property values programmatically (rather than declaratively), then it sort of diminishes the value of using ds in general, but I may just not be aware of the simplest way to set service properties using DS.
> 
> Scott
> 
> 
> 
>> Would the IContainer implementation listen for my custom service registrations and check according to the provided properties whether to publish a service remotely or not? I've done this so far programmatically using the IContainer instance: 
>> public synchronized void registerRemoteService(String serviceName, Object impl,
>> ID[] targetIDs) {
>> 
>> Dictionary<String, ID[]> props = new Hashtable<String, ID[]>();
>> props.put(Constants.SERVICE_REGISTRATION_TARGETS, targetIDs);
>> 
>> // register ECF remote service
>> getRemoteServiceContainerAdapter().registerRemoteService(
>> new String[] { serviceName }, impl, props);
>> }
>> 
>> In case this is the way it works: can I provide the properties above at runtime using the ConfigAdminService?
>> 
>>>> On the client side the hello.ds.consumer bundle only describes that it requires an IHello service. But I was not able to find out how the service is configured: server url, username, password.
>>> 
>>> The server url and username (for xmpp) would be conveyed in what's called the 'endpointID' in discovery...e.g. for the XMPP provider the endpoint id would be an XMPP account:  'scottslewis@xxxxxxxxx <mailto:%27scottslewis@xxxxxxxxx>'
>> 
>> My question focused on how do consumer retrieve a remote service proxy  using DS. So far I wrote a util class that can retrieve remote services for a specific XMPP ID in this way:
>> 
>> public synchronized <T> List<T> getRemoteService(Class<T> service, ID[] filterIDs,
>> String filter) throws ECFException, InvalidSyntaxException {
>> List<T> remoteServices = new ArrayList<T>();
>> 
>> IRemoteServiceContainerAdapter remoteServiceContainerAdapter = getRemoteServiceContainerAdapter();
>> 
>> /* 1. get available services */
>> IRemoteServiceReference[] refs = remoteServiceContainerAdapter
>> .getRemoteServiceReferences(filterIDs, service.getName(),
>> filter);
>> 
>> /* 2. get the proxies for service references */
>> for (int serviceNumber = 0; serviceNumber < refs.length; serviceNumber++) {
>> 
>> IRemoteService remoteService = remoteServiceContainerAdapter
>> .getRemoteService(refs[serviceNumber]);
>> 
>> T castedService = service.cast(remoteService.getProxy());
>> assert castedService != null : "castedService != null";
>> remoteServices.add(castedService);
>> }
>> 
>> return remoteServices;
>> }
>> 
>> What I did not understand is how does DS retrieve remote service proxies? As OSGi has no standard way to provide credentials I guess this won't work for me using XMPP. But in case the IContainer solution I've mentioned above works, could it be a solution to:
>> 1. create an IContainer instance with credentials
>> 2. register the instance as an OSGi service in a client side service registry
>> 2. create DS components with the properties I've mentioned above?
>> 
>> I'm sorry for all these questions but it is still difficult for me to understand how ECF works with DS  and whether I can use it for my work.
>> 
>> Regards,
>> Eugen
>> 
>> 
>> Am Jun 14, 2010 um 15:54  schrieb Scott Lewis:
>> 
>>> Hi Eugen,
>>> 
>>> Eugen Reiswich wrote:
>>>> Hi Scott, hi Wim,
>>>> 
>>>> I've also checked out the hello.ds examples and tried to understand how this example works. As I understand the host has to provide specific server properties within the service component. And as far as I understand these properties will trigger ECF to make a service remotely available (I didn't really get what exactly happens here).  In my XMPP case I've changed the example to:
>>>> <property name="service.exported.interfaces" type="String" value="*"/>
>>>> <property name="service.exported.configs" type="String" value="ecf.xmpp.smack"/>
>>>> <property name="org.eclipse.ecf.containerFactoryArgs" type="String" value="jabber-server.de <http://jabber-server.de> <http://-server.de>"/>
>>>> 
>>>> If I got you right Scott I do no longer need to create an instance of IContainer like I did before:
>>>> IContainer container = ContainerFactory.getDefault().createContainer(protocol);
>>>> XMPPID xmppid = new XMPPID(container.getConnectNamespace(), userName + "@" + server);
>>>> IConnectContext connectContext = ConnectContextFactory.createUsernamePasswordConnectContext(userName, password);
>>>> container.connect(xmppid, connectContext);
>>>> 
>>>> But how do I now  provide with DS username and password in order to be able to connect to a server?
>>> 
>>> Because OSGi 4.2 provides no standard way to provide credentials for connect authentication, and the XMPP provider and service obviously require such credentials, it's necessary to use some mechanism not exposed by OSGi service properties.  There are a couple of ways provided by ECF's implementation:
>>> 
>>> 1) An explicit connect...i.e. the code that you use above to create and connect a container. 2) Implementing and registering your own IProxyContainerFinder, so that upon discovery (and container creation, and connect), that your credentials can be provided.
>>> 
>>> I would say, that given that you already have the code for 1, that 1 seems more appropriate for you...since you are already doing it.
>>> 
>>> Nevertheless, I'll describe 2 a little bit here.  The easiest way to customize the proxy container find/creation/connect is to extend  org.eclipse.ecf.osgi.services.distribution.DefaultProxyContainerFinder and override this method (actually declared in AbstractProxyContainerFinder):
>>> 
>>>  protected IConnectContext getConnectContext(IContainer container,
>>>          ID connectTargetID) {
>>>      return null;
>>>  }
>>> 
>>> Then, to have your proxy container finder used rather than the default one, simply register your instance as an OSGi service using the whiteboard pattern:
>>> 
>>> context.registerService(IProxyContainerFinder.class.getName(), new MyProxyContainerFinder(), null);
>>> (you can do the registration with ds if you prefer)
>>> 
>>> By overriding this method (getConnectContext) you can provide the appropriate credentials for the connect that happens within the proxy container finder during the OSGi 4.2 consumer-side discovery.  So, for example
>>> 
>>> public class MyProxyContainerFinder extends DefaultProxyContainerFinder {
>>> 
>>> private String username;
>>> private String password;
>>> 
>>> public MyProxyContainerFinder(String username, String password) {
>>> this.username = username;
>>> this.password = password;
>>> }
>>> 
>>> protected IConnectContext getConnectContext(IContainer container, ID connectTargetID) {
>>> if (container and connectTargetID are appropriate types/values)
>>>    return ConnectContextFactory.createUsernamePasswordConnectContext(this.username, this.password);
>>> }
>>> 
>>> }
>>> 
>>> There is a little more documentation on the host and proxy container finder...and customizing the ECF impl of OSGi remote services:
>>> 
>>> http://wiki.eclipse.org/Customizing_and_Extending_ECF_Remote_Services
>>> 
>>> Note that depending upon how you are doing host registration and discovery, you will also have to specify (in service properties) the connectTargetID...which is used by the DefaultProxyContainerFinder.findProxyContainers method (which ultimately calls the getConnectContext when creating and then connecting a container).
>>> So like I mentioned before, it seems to me that method 1 may be the easiest/quickest route for you (rather than method 2), but I wanted to use the opportunity to explain method 2 a little, since it provides quite a lot of flexibility for use cases that don't fit into the default OSGi 4.2 rs mold...like the use of XMPP.
>>> 
>>> Incidently...another thing to mention...it's also possible to override DefaultProxyContainerFinder.findProxyContainers and change the entire implementation of how an ECF container is selected/created/connected during OSGi 4.2 discovery.  For some use cases, this might be desired.
>>> 
>>>> 
>>>> On the client side the hello.ds.consumer bundle only describes that it requires an IHello service. But I was not able to find out how the service is configured: server url, username, password.
>>> 
>>> The server url and username (for xmpp) would be conveyed in what's called the 'endpointID' in discovery...e.g. for the XMPP provider the endpoint id would be an XMPP account:  'scottslewis@xxxxxxxxx'
>>> 
>>> Scott
>>> 
>>> 
>>> _______________________________________________
>>> ecf-dev mailing list
>>> ecf-dev@xxxxxxxxxxx
>>> https://dev.eclipse.org/mailman/listinfo/ecf-dev
>> 
>> ------------------------------------------------------------------------
>> 
>> _______________________________________________
>> ecf-dev mailing list
>> ecf-dev@xxxxxxxxxxx
>> https://dev.eclipse.org/mailman/listinfo/ecf-dev
>>  
> 
> _______________________________________________
> ecf-dev mailing list
> ecf-dev@xxxxxxxxxxx
> https://dev.eclipse.org/mailman/listinfo/ecf-dev