-
Notifications
You must be signed in to change notification settings - Fork 67
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add convenient way to get rarely fetched and locally used OSGi services #146
base: master
Are you sure you want to change the base?
Conversation
* Gets the first available service implementation. | ||
*/ | ||
@Override | ||
S get() throws NoSuchElementException; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would suggest to use an optional here, thats much more convenient and flexible and one can use the "orThrows" methods to give meaningful exception/error messages.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought about that as well and actually wanted ServiceUsage
to be an Optional
, but since Optional
is final this is not feasible (even if ServiceUsage
would not be an interface).
Therefore my goal was to mimic Optional here with a similar get()
method and isAvailable() (which also can be renamed to isPresent()
).
But making it a Supplier that returns an Optional seems to be a bit inconvenient for me, because to eventually get the service instance one has to call serviceUsage.get().get()
(if the service must be there), which I don't find nice.
But if, please don't use Exceptions to signal missing services, this gives very very bad error reporting, better use Optionals so the caller of that method can give good errors or even use defaults if a service is missing.
Is there another default that could be used than null
? I think returning null is not desired, for the same reasons why one should use Optional over null. Furthermore I think the error message that is thrown by the ServiceUsage
implementation is more descriptive than the one of Optional
because it explicitly mentions the unavailable service.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you can return null or throw exception you can return an optional. Calling get() on Optional
is actually not recommended, you should use orElse or orElseThrow to either handle or signal a missing item. Also the method name "get" is jsut the choice of supplier, as you implement an interface here you can always choose a better name like service
or fetch
as a method name.
Actually I think So I personally not very convinced we should enhance |
Of course this is not the most performant way to get a service instance and it is definitely not a good idea to use that method inside a hot loop. The goal of this method is to provide a simple and save way to obtain a service instance in situations where for example OSGi DS or a ServiceTracker is not applicable or useful. I will clearly document that intend and the performance considerations. Furthermore the pattern to get/unget a service for a single use is already used in multiple more or less correct variations. And the suggested new method is IMHO a far more better way than for example what PDE (and IIRC P2 as well) does: And there are more examples:
I'm fine in adding a overload with an additional BundleContext parameter. But why is using FrameworkUtil.getBundle(Class) a problem for OSGi-Connect? Isn't it the purpose of @tjwatson or whoever is interested, can you please also give your general assessment about this proposal, too? |
DI (and its OSGi implementation DS) is always useful and/or applicable and If I need to access a service statically and only once, then something is probably wrong. For example I then probably better get it passed as a parameter instead of struggling with it myself.
So these places are better refactored than adding another variant of a bad pattern.
It is but that's just horrible complex because it requires once classloader = one bundle, while in connect many bundles can share the same classloader, or even have foreign classloaders. Aslo in almost all cases one don'T want to get the bundle but (as here as well) the bundle context and the javadoc of BundleContext clearly states:
And as a bundle has always access to its bundle context (e.g. DS or Activators) there is really no need to use |
The opening comment gives this example code: BundleContext ctx = FrameworkUtil.getBundle(Demo.class).getBundleContext();
ServiceReference<MyService> ref = ctx.getServiceReference(MyService.class);
MyService myService = ctx.getService(ref);
try {
myService.doSomething();
} finally {
ctx.ungetService(ref);
} And it can be simplified to this with the proposal: try (var service = ServiceCaller.useService(MyService.class)) {
service.get().doSomething();
} But the existing ServiceCaller.callOnce(Demo.class, MyService.class, MyService::doSomething); I'm not convinced a new method for calling a rarely used service is necessary. |
The opening example is indeed chosen badly in this regard.
with the new method one can just call for example
or assign the return value to another variable and process it further outside the try-block.
Alternatively a
This would indeed make simple computation a one liner if a possible exception should not be catched:
But if the exception should be catched a try-catch block has to be added again and it would IMHO be more readable with the new method. Another advantage of the new approach is the |
throws an exception if there is no service. This seems a bit harsh. I think you may be better off with an overload of the callonce method which is exception friendly. |
0719121
to
21f16fa
Compare
After thinking about it a little more and considering the alternatives to add If we would add more overloads that would lead to a combinatorics explosion if we want to provide means to obtain a Service with and without filter, from the caller class or a given context, want to use only the first or all serivces.
The only real downside to the previous approach is that the Optional does not know the service name if it throws an exception in case of a absent service. Previously the NSE which was thrown by the |
*/ | ||
public static <S> ServiceUsageScope<S> use(Class<S> service, String filter, BundleContext context) | ||
throws InvalidSyntaxException { | ||
ServiceReference<?>[] references = context.getServiceReferences(service.getName(), filter); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This result in unordered which means the all
stream will report the ordered characteristic incorrectly and the first
optional will hold something which is not the "first" as there is no ordering.
So you should sort the references in reverse order so the highest ranked service is first.
ServiceReference<?>[] references = context.getServiceReferences(service.getName(), filter); | |
ServiceReference<?>[] references = context.getServiceReferences(service.getName(), filter); | |
Arrays.sort(references, Comparator.reverseOrder()); |
This means first
will always be the highest ranked service and all
will be ordered so that higher ranked services come before lower ranked services.
You may also want to rename first
to highest
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This means
first
will always be the highest ranked service andall
will be ordered so that higher ranked services come before lower ranked services.
That's a good suggestion, thank you.
But if I'm not missing anything, for example the result of the BundleContext.getServiceReference(Class)
does not consider the service ranking does it?
You may also want to rename
first
tohighest
.
It would indeed by good to reflect that in the name, but I'm not so happy with the name highest()
. But at the moment I don't have a better one.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But if I'm not missing anything, for example the result of the
BundleContext.getServiceReference(Class)
does not consider the service ranking does it?
It does not. The order of the returned ServiceReferences is unspecified. So you need to sort them.
21f16fa
to
f52e1bb
Compare
Optional<S> first(); | ||
|
||
Stream<S> all(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The javadoc on these methods need more precision. first should state it returns the highest ranked service, if there are any services. all should state the stream is ordered in ranking order (highest ranked service first, lowest ranked service last).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
/** | ||
* Gets the first available service implementation. | ||
*/ | ||
Optional<S> first(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
first can be default implemented as
Optional<S> first(); | |
default Optional<S> first() { | |
return all().findFirst(); | |
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
True, but realistically there will only ever be one implementation of the interface so there is no real value in using a default method.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this be a public final class with a package accessible constructor so nothing else can construct it? That makes it clear there are no other implementations.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this be a public final class with a package accessible constructor so nothing else can construct it? That makes it clear there are no other implementations.
Done.
I updated the implementation and added another ServiceInstance class that allows a user to also query the properties of the service registrations. Besides that I also added documentation to all methods and nested classes and applied the suggested code-changes. What's missing from my point, is a another usage example in the ServiceCaller javadoc and some tests. What do the others think? |
To obtain an instance of a OSGi service often the very basic methods
BundleContext.getServiceReference()
andBundleContext.(un-)getService()
are called directly.For example the PDE or P2 code base often contain something like the following code snippet:
This is cumbersome and error prone (null-checks are often missing like in the given example). In some cases the service instance is even used outside the try-block. Using the service instance after releasing the reference leads to an incorrect use-count of the service reference, which can lead to follow up errors and maybe even classloader leaks.
Therefore I want to propose a convenient and save way to obtain an instance of a given service that are rarely fetched or where performance does not matter and therefore a
ServiceTracker
is not necessary or wanted.The idea is to add new methods to
ServiceCaller
that returns a auto-closeable supplier of the service instance that can be used in a try-with-resources block. Within the block the get() method returns the service instance and when the block is left the service reference is unget:Of course this does fully prevent one from using the service instance outside the try-block. The intend is to provide methods that make it so simple to obtain a service that such error prone 'convenience hacks' are not necessary.
The new methods are lacking of proper documentation and tests and the names can probably/maybe improved. Nevertheless I wanted to gather general feedback about this proposal. Please let me know if you have improvements.