1

Fellow coders,

I'm currently trying to find a simple and concise way to get a listing of of Services/Components that use a given Interface. I'm using the gogo-shell of a running Liferay 7.1.x server and can't seem to find an easy and direct way to to just that.

We want to override references to the used service via OSGI-configuration, but first need to find all components using it. As there are static reluctant references to the service component, simply providing an alternative with a higher ranking is not a viable solution.

Here are the gogo related bundles I'm using:

   35|Active     |    6|Apache Felix Gogo Command (1.0.2)|1.0.2
   36|Active     |    6|Apache Felix Gogo Runtime (1.1.0.LIFERAY-PATCHED-2)|1.1.0.LIFERAY-PATCHED-2
   72|Active     |    6|Apache Felix Gogo Shell (1.1.0)|1.1.0
  542|Active     |   10|Liferay Foundation - Liferay Gogo Shell - Impl (1.0.13)|1.0.13
  543|Active     |   10|Liferay Gogo Shell Web (2.0.25)|2.0.25

So far I've been able to list all providers of an interface via se (interface=com.liferay.saml.runtime.servlet.profile.WebSsoProfile):

{com.liferay.saml.runtime.profile.WebSsoProfile, com.liferay.saml.runtime.servlet.profile.WebSsoProfile}={service.id=6293, service.bundleid=79, service.scope=bundle, component.name=com.liferay.saml.opensaml.integration.internal.servlet.profile.WebSsoProfileImpl, component.id=3963}
  "Registered by bundle:" de.haufe.leong.com.liferay.saml.opensaml.integration [79]
  "Bundles using service"
    com.liferay.saml.web_2.0.11 [82]
    com.liferay.saml.impl_2.0.12 [78]

See all bundle requirements via: inspect cap service:

com.liferay.saml.impl_2.0.12 [78] requires:
...
service; com.liferay.saml.runtime.profile.WebSsoProfile, com.liferay.saml.runtime.servlet.profile.WebSsoProfile provided by:
   de.haufe.leong.com.liferay.saml.opensaml.integration [79]
...

But listing the actual services from within these bundles that use the given interface (or the service component) has eluded me so far.

The only solution I see so far is listing all provided services of these bundles via scr:list bid and then check each service with scr:info componentId to see if it uses the WebSsoProfile service.

Do you guys know a faster way to find the services using the WebSsoProfile-service?


[EDIT]: We solved the problem without having to provide config overrides for all consumers of the WebSsoProfile service but rather ensure that our implementation is used by deactivating the default service on Server startup. You can see the approach described here.

Anyways for debugging purposes this kind of lookup would be very useful. So if anyone knows a way to retrieve the list of all consumers of an interface then please post your solution!

Community
  • 1
  • 1
Nuw
  • 11
  • 3

1 Answers1

2

The standard solution is using the inspect command. It has a special namespace for services. Since a service registration is a capability, you can use inspect capability service:

g! inspect c service
org.apache.felix.framework [0] provides:
----------------------------------------
service; org.osgi.service.resolver.Resolver with properties:
   service.bundleid = 0
   service.id = 1
   service.scope = singleton
service; org.osgi.service.packageadmin.PackageAdmin with properties:
   service.bundleid = 0
   service.id = 2
   service.scope = singleton
service; org.osgi.service.startlevel.StartLevel with properties:
   service.bundleid = 0
   service.id = 3
   service.scope = singleton

....

However, I find this command seriously useless. The command is inflexible and has a horrible output.

However, Gogo is way more powerful than people know. For one, you can use all the methods on the bundle context.

g! servicereferences org.osgi.service.startlevel.StartLevel null
000003   0 StartLevel

If you want to see the properties of each service:

g! each (servicereferences org.osgi.service.startlevel.StartLevel null) { $it properties }
[service.id=3, objectClass=[Ljava.lang.String;@4acd14d7, service.scope=singleton, service.bundleid=0]

You can make this into a built-in function:

g! srv = { servicereferences $1 null }
servicereferences $1 null
g! srv org.osgi.service.startlevel.StartLevel
000003   0 StartLevel                               

Unfortunately, the OSGi added an overloaded method in the Bundle Context for getServiceReferences() that throws an NPE when called with null. Gogo is awful with overloaded methods :-(

However, it is trivial to add your own command with a declarative service component. You could use the following:

@GogoCommand(scope="service", function="srv")
@Component(service=ServiceCommand.class)
public class ServiceCommand {

    @Activate
    BundleContext context;        

    @Descriptor("List all services")
    public ServiceReference<?>[] srv() throws InvalidSyntaxException {
        return context.getAllServiceReferences(null, null);
    }

    @Descriptor("List all services that match the name")
    public ServiceReference<?>[] srv(
            String... names)
            throws InvalidSyntaxException {
        ServiceReference<?>[] allServiceReferences = 
            context.getAllServiceReferences(null,null);
        if ( allServiceReferences==null)
            return new ServiceReference[0];
        return Stream.of(allServiceReferences)
                .filter(r -> {
                    String[] objectClass = (String[]) r.getProperty(Constants.OBJECTCLASS);
                    for (String oc : objectClass) {
                        for (String name : names)
                            if (oc.contains(name))
                                return true;
                    }
                    return names.length == 0;
                }).sorted().toArray(ServiceReference[]::new);
    }
}

This adds the srv command to Gogo:

g! srv Help Basic
000004   1 Basic                                    
000005   1 Inspect   

Update If you want to find which bundles are using a specific service, you could use:

g! each (srv X) { $it usingbundles }

Make sure you got the following dependencies on your classpath:

-buildpath: \
    org.osgi.service.component.annotations,\
    org.apache.felix.gogo.runtime, \
    org.osgi.framework
Peter Kriens
  • 15,196
  • 1
  • 37
  • 55
  • Thank you for your elaborate reply @peter-kriens! Please correct me if I'm wrong but your example only lists all services that are registered under a certain name and that's what gogo can already provide. Do you have any insights into how I could get all services referencing a given Interface name using standard gogo shell features? If there aren't any then I think the best approach would be to write an own command. But this seems like such a common use-case that I thought that there must be a way to get this out of the box. – Nuw Feb 18 '20 at 08:13
  • I agree that it feels weird that you cannot get _all_ services. However, the newer API overloaded the `getServiceReferences(String,String)` method with `getServiceReferences(Class,String)`. The first form accepts (null,null), the second requires a non-null value. Unfortunately, Gogo did not allow the selection of one of overloaded methods. So it was not an oversight (it worked), it is just the normal average software rot ... And the core Gogo seems to not have changed a lot the last 10 years. – Peter Kriens Feb 18 '20 at 14:04
  • I understood your point about the API changes of the BundleContext. But you solution doesn't solve my problem. I do not need to find all services that are registered under a certain name. I need the services that have a reference to a service that is registered under a name I know. – Nuw Feb 20 '20 at 07:40
  • A service does not have a reference to a service, a bundle has. So you could do: `each ( srv X ) { $it usingbundles }` – Peter Kriens Feb 20 '20 at 13:10