001/*
002 * Copyright (C) 2019 Michael N. Lipp (http://www.mnl.de)
003 * 
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *        http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package de.mnl.osgi.coreutils;
018
019import java.util.HashMap;
020import java.util.Map;
021import java.util.Optional;
022import java.util.function.Consumer;
023
024import org.osgi.framework.BundleActivator;
025import org.osgi.framework.BundleContext;
026import org.osgi.framework.Filter;
027import org.osgi.framework.ServiceReference;
028
029/**
030 * Maintains and attempts to resolve dependencies on services.
031 * <P>
032 * The class supports two usage pattern. The first is to use the
033 * {@code ServiceResolver} as base class for the bundle's activator.
034 * The derived class must override {@link #configure()} to add
035 * at least one dependency.
036 * <P>
037 * The methods {@link #onResolved()}, {@link #onDissolving()} and
038 * {@link #onRebound(String)} can be overridden as required.
039 * 
040 * <pre>
041 * public class Activator extends ServiceResolver {
042 *     &#x40;Override
043 *     protected void configure() {
044 *         addDependency(...);
045 *     }
046 *     
047 *     &#x40;Override
048 *     protected void onResolved() {
049 *         // Enable usage or register as service or start worker thread ...
050 *     }
051 *     
052 *     &#x40;Override
053 *     protected void onDissolving() {
054 *         // Disable usage or unregister or stop and join worker thread ...
055 *     }
056 *     
057 *     &#x40;Override
058 *     protected void onRebound(String dependency) {
059 *         // Only required if there is a "long term" usage of a dependency.
060 *     }
061 * }
062 * </pre>
063 * 
064 * The second usage pattern is to create a {@code ServiceResolver} with
065 * a {@link BundleContext}, add dependencies and callbacks as required
066 * and call {@link #open()}. When resolving is no longer required,
067 * {@link #close()} must be called.
068 * 
069 * <pre>
070 * // Usually executed during bundle start...
071 * ServiceResolver resolver = new ServiceResolver(context);
072 * // Add dependencies
073 * resolver.addDependency(...);
074 * resolver.setOnResolved(() -&gt; ...);
075 * resolver.setOnDissolving(() -&gt; ...);
076 * resolver.setOnRebound(name -&gt; ...);
077 * resolver.open();
078 * 
079 * // Usually executed during bundle stop...
080 * resolver.close();
081 * </pre>
082 * 
083 * In addition to the management of mandatory dependencies, the
084 * {@code ServiceResolver} can also keep track of optional
085 * service dependencies. This allows the {@code ServiceResolver}
086 * to be provided as unified service access point. Apart from
087 * a single {@link #open()} and {@link #close()} function, the
088 * {@code ServiceProvider} does not implement additional functions
089 * for optional service dependencies compared to the underlying
090 * {@link ServiceCollector}s.
091 */
092public class ServiceResolver implements AutoCloseable, BundleActivator {
093
094    /**
095     * The bundle context. Made available for derived classes.
096     */
097    protected BundleContext context;
098    private volatile boolean isOpen;
099    private Map<String, ServiceCollector<?, ?>> dependencies = new HashMap<>();
100    private Map<String, ServiceCollector<?, ?>> optDependencies
101        = new HashMap<>();
102    private int resolvedCount;
103    private Runnable onResolved;
104    private Runnable onDissolving;
105    private Consumer<String> onRebound;
106
107    /**
108     * Creates a new resolver that uses the given context.
109     *
110     * @param context the context
111     */
112    public ServiceResolver(BundleContext context) {
113        this.context = context;
114    }
115
116    /**
117     * Constructor for using the {@code ServiceResolver} as base
118     * class for a {@link BundleActivator}.
119     */
120    protected ServiceResolver() {
121    }
122
123    /**
124     * Called by the framework when using the {@code ServiceResolver} as 
125     * base class for a bundle activator.
126     * <P>
127     * The implementation sets the {@link #context} attribute and calls 
128     * {@link #configure()} and {@link #open()}. 
129     *
130     * @param context the context
131     * @throws Exception if a problem occurs
132     */
133    @Override
134    public void start(BundleContext context) throws Exception {
135        this.context = context;
136        configure();
137        open();
138    }
139
140    /**
141     * Configures the {@code ServiceResolver}. Must be overridden 
142     * by the derived class when using the {@code ServiceResolver}
143     * as base class for a bundle activator.
144     * The derived class must configure the resolver with calls to
145     * at least one of the {@code addDependency} methods.
146     * <P>
147     * The default implementation does nothing.
148     */
149    protected void configure() {
150    }
151
152    /**
153     * Called when all dependencies have been resolved. Overriding this
154     * method is an alternative to setting a callback with
155     * {@link #setOnResolved(Runnable)}.
156     */
157    protected void onResolved() {
158    }
159
160    /**
161     * Called when the resolver is about to leave the resolved state,
162     * i.e. when one of the mandatory services is going to be unbound.
163     * Overriding this method is an alternative to setting a callback with
164     * {@link #setOnDissolving(Runnable)}.
165     */
166    protected void onDissolving() {
167    }
168
169    /**
170     * Called when the preferred service of a resolved dependency 
171     * changes. The change may either be a change of properties 
172     * reported by the framework or the replacement of the preferred 
173     * service with another service. Overriding this method is an 
174     * alternative to setting a callback with
175     * {@link #setOnRebound(Consumer)}.
176     *
177     * @param dependency the dependency that has been rebound
178     */
179    protected void onRebound(String dependency) {
180    }
181
182    /**
183     * Called by the framework when using the {@code ServiceResolver} as 
184     * base class for a bundle activator. The implementatoin calls
185     * {@link #close()}.
186     *
187     * @param context the context
188     * @throws Exception if a problem occurs
189     */
190    @Override
191    public void stop(BundleContext context) throws Exception {
192        close();
193    }
194
195    /**
196     * Sets the function to called when the resolver has entered the
197     * resolved state, i.e. when all mandatory services have been bound.
198     *
199     * @param onResolved the function
200     * @return the service resolver
201     */
202    public ServiceResolver setOnResolved(Runnable onResolved) {
203        this.onResolved = onResolved;
204        return this;
205    }
206
207    /**
208     * Sets the function to called when the resolver is about to leave the
209     * resolved state, i.e. when one of the mandatory services is going
210     * to be unbound.
211     *
212     * @param onDissolving the function
213     * @return the service resolver
214     */
215    public ServiceResolver setOnDissolving(Runnable onDissolving) {
216        this.onDissolving = onDissolving;
217        return this;
218    }
219
220    /**
221     * Sets the function to be called when the preferred service
222     * of a resolved dependency changes. The change may either be
223     * a change of properties reported by the framework or the
224     * replacement of the preferred service with another service. 
225     *
226     * @param onRebound the on rebound
227     * @return the service resolver
228     */
229    public ServiceResolver setOnRebound(Consumer<String> onRebound) {
230        this.onRebound = onRebound;
231        return this;
232    }
233
234    /**
235     * Adds a mandatory dependency on the service specified by the
236     * class. The name of the class is used as name of the
237     * dependency.
238     *
239     * @param clazz the class
240     * @return the service resolver
241     */
242    public ServiceResolver addDependency(Class<?> clazz) {
243        addDependency(clazz.getName(), clazz.getName());
244        return this;
245    }
246
247    /**
248     * Adds a mandatory dependency on the service specified by the
249     * filter.
250     *
251     * @param name the name of the dependency
252     * @param filter the filter
253     * @return the service resolver
254     */
255    public ServiceResolver addDependency(String name, Filter filter) {
256        synchronized (this) {
257            if (isOpen) {
258                throw new IllegalStateException(
259                    "Cannot add dependencies to open reolver.");
260            }
261            dependencies.put(name, new ServiceCollector<>(context, filter)
262                .setOnBound(this::boundCb).setOnUnbinding(this::unbindingCb)
263                .setOnModfied((ref, svc) -> modifiedCb(name)));
264            return this;
265        }
266    }
267
268    /**
269     * Adds a mandatory dependency on the service specified by the
270     * service reference.
271     *
272     * @param name the name of the dependency
273     * @param reference the reference
274     * @return the service resolver
275     */
276    public ServiceResolver addDependency(String name,
277            ServiceReference<?> reference) {
278        synchronized (this) {
279            if (isOpen) {
280                throw new IllegalStateException(
281                    "Cannot add dependencies to open reolver.");
282            }
283            dependencies.put(name, new ServiceCollector<>(context, reference)
284                .setOnBound(this::boundCb).setOnUnbinding(this::unbindingCb)
285                .setOnModfied((ref, svc) -> modifiedCb(name)));
286            return this;
287        }
288    }
289
290    /**
291     * Adds a mandatory dependency on the service specified by the
292     * service reference.
293     *
294     * @param name the name of the dependency
295     * @param className the class name
296     * @return the service resolver
297     */
298    public ServiceResolver addDependency(String name, String className) {
299        synchronized (this) {
300            if (isOpen) {
301                throw new IllegalStateException(
302                    "Cannot add dependencies to open reolver.");
303            }
304            dependencies.put(name, new ServiceCollector<>(context, className)
305                .setOnBound(this::boundCb).setOnUnbinding(this::unbindingCb)
306                .setOnModfied((ref, svc) -> modifiedCb(name)));
307            return this;
308        }
309    }
310
311    /**
312     * Adds an optional dependency on the service specified by the
313     * class. The name of the class is used as identifier for the
314     * dependency.
315     *
316     * @param clazz the class
317     * @return the service resolver
318     */
319    public ServiceResolver addOptionalDependency(Class<?> clazz) {
320        addOptionalDependency(clazz.getName(), clazz.getName());
321        return this;
322    }
323
324    /**
325     * Adds an optional dependency on the service specified by the
326     * filter.
327     *
328     * @param name the name of the dependency
329     * @param filter the filter
330     * @return the service resolver
331     */
332    public ServiceResolver addOptionalDependency(String name, Filter filter) {
333        synchronized (this) {
334            if (isOpen) {
335                throw new IllegalStateException(
336                    "Cannot add dependencies to open reolver.");
337            }
338            optDependencies.put(name, new ServiceCollector<>(context, filter)
339                .setOnModfied((ref, svc) -> modifiedCb(name)));
340            return this;
341        }
342    }
343
344    /**
345     * Adds an optional dependency on the service specified by the
346     * service reference.
347     *
348     * @param name the name of the dependency
349     * @param reference the reference
350     * @return the service resolver
351     */
352    public ServiceResolver addOptionalDependency(String name,
353            ServiceReference<?> reference) {
354        synchronized (this) {
355            if (isOpen) {
356                throw new IllegalStateException(
357                    "Cannot add dependencies to open reolver.");
358            }
359            optDependencies.put(name,
360                new ServiceCollector<>(context, reference)
361                    .setOnModfied((ref, svc) -> modifiedCb(name)));
362            return this;
363        }
364    }
365
366    /**
367     * Adds an optional dependency on the service specified by the
368     * service reference.
369     *
370     * @param name the name of the dependency
371     * @param className the class name
372     * @return the service resolver
373     */
374    public ServiceResolver addOptionalDependency(String name,
375            String className) {
376        synchronized (this) {
377            if (isOpen) {
378                throw new IllegalStateException(
379                    "Cannot add dependencies to open reolver.");
380            }
381            optDependencies.put(name,
382                new ServiceCollector<>(context, className)
383                    .setOnModfied((ref, svc) -> modifiedCb(name)));
384            return this;
385        }
386    }
387
388    private void boundCb(ServiceReference<?> reference, Object service) {
389        synchronized (this) {
390            resolvedCount += 1;
391            if (resolvedCount == dependencies.size()) {
392                Optional.ofNullable(onResolved).ifPresent(cb -> cb.run());
393                onResolved();
394            }
395        }
396    }
397
398    private void unbindingCb(ServiceReference<?> reference, Object service) {
399        synchronized (this) {
400            if (resolvedCount == dependencies.size()) {
401                Optional.ofNullable(onDissolving).ifPresent(cb -> cb.run());
402                onDissolving();
403            }
404            resolvedCount -= 1;
405        }
406    }
407
408    private void modifiedCb(String dependency) {
409        synchronized (this) {
410            Optional.ofNullable(onRebound)
411                .ifPresent(cb -> cb.accept(dependency));
412            onRebound(dependency);
413        }
414    }
415
416    /**
417     * Starts the resolver. While open, the resolver attempts
418     * to obtain service implementation for all registered
419     * service dependencies.
420     */
421    public void open() {
422        synchronized (this) {
423            if (isOpen) {
424                return;
425            }
426            resolvedCount = 0;
427        }
428        for (ServiceCollector<?, ?> coll : dependencies.values()) {
429            coll.open();
430        }
431        for (ServiceCollector<?, ?> coll : optDependencies.values()) {
432            coll.open();
433        }
434        isOpen = true;
435    }
436
437    /**
438     * Stops the resolver. All maintained services are released.
439     */
440    public void close() {
441        synchronized (this) {
442            if (!isOpen) {
443                return;
444            }
445            isOpen = false;
446            for (ServiceCollector<?, ?> coll : dependencies.values()) {
447                coll.close();
448            }
449            for (ServiceCollector<?, ?> coll : optDependencies.values()) {
450                coll.close();
451            }
452            dependencies.clear();
453            optDependencies.clear();
454        }
455    }
456
457    /**
458     * Checks if the resolver is open.
459     *
460     * @return true, if open
461     */
462    public boolean isOpen() {
463        return isOpen;
464    }
465
466    /**
467     * Checks if the resolver is in the resolved state.
468     *
469     * @return the result
470     */
471    public boolean isResolved() {
472        synchronized (this) {
473            return resolvedCount == dependencies.size();
474        }
475    }
476
477    /**
478     * Returns the service found for the mandatory dependency with the
479     * given name. This method should only be called when the resolver
480     * is in state resolved. 
481     *
482     * @param <T> the type of the service
483     * @param name the name of the dependency
484     * @param clazz the class of the service
485     * @return the service or {@code null} if called in unresolved
486     *     state and the dependency is not resolved
487     */
488    @SuppressWarnings("unchecked")
489    public <T> T get(String name, Class<T> clazz) {
490        return Optional.ofNullable(dependencies.get(name))
491            .map(coll -> ((ServiceCollector<?, T>) coll).service().get())
492            .orElse(null);
493    }
494
495    /**
496     * Returns the service found for the mandatory dependency
497     * using the name of the class as name of the dependency. 
498     * This method should only be called when 
499     * the resolver is in state resolved. 
500     *
501     * @param <T> the type of the service
502     * @param clazz the class of the service
503     * @return the service or {@code null} if called in unresolved
504     *     state and the dependency is not resolved
505     */
506    public <T> T get(Class<T> clazz) {
507        return get(clazz.getName(), clazz);
508    }
509
510    /**
511     * Returns the (optional) service found for the optional dependency 
512     * with the given name. 
513     * 
514     * @param <T> the type of the service
515     * @param name the name of the dependency
516     * @param clazz the class of the service
517     * @return the result
518     */
519    @SuppressWarnings("unchecked")
520    public <T> Optional<T> optional(String name, Class<T> clazz) {
521        return Optional.ofNullable(dependencies.get(name))
522            .map(coll -> ((ServiceCollector<?, T>) coll).service())
523            .orElse(Optional.empty());
524    }
525
526    /**
527     * Returns the (optional) service found for the optional dependency, 
528     * using the name of the class as name of the dependency. 
529     *
530     * @param <T> the type of the service
531     * @param clazz the class of the service
532     * @return the result
533     */
534    public <T> Optional<T> optional(Class<T> clazz) {
535        return optional(clazz.getName(), clazz);
536    }
537}