001/* 002 * Copyright (C) 2019,2022 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; 023import java.util.function.Function; 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 * @Override 043 * protected void configure() { 044 * addDependency(...); 045 * } 046 * 047 * @Override 048 * protected void onResolved() { 049 * // Enable usage or register as service or start worker thread ... 050 * } 051 * 052 * @Override 053 * protected void onDissolving() { 054 * // Disable usage or unregister or stop and join worker thread ... 055 * } 056 * 057 * @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(() -> ...); 075 * resolver.setOnDissolving(() -> ...); 076 * resolver.setOnRebound(name -> ...); 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 */ 092@SuppressWarnings({ "PMD.GodClass", "PMD.TooManyMethods" }) 093public class ServiceResolver implements AutoCloseable, BundleActivator { 094 095 /** 096 * The bundle context. Made available for derived classes. 097 */ 098 protected BundleContext context; 099 @SuppressWarnings("PMD.AvoidUsingVolatile") 100 private volatile boolean isOpen; 101 @SuppressWarnings("PMD.UseConcurrentHashMap") 102 private final Map<String, ServiceCollector<?, ?>> dependencies 103 = new HashMap<>(); 104 @SuppressWarnings("PMD.UseConcurrentHashMap") 105 private final Map<String, ServiceCollector<?, ?>> optDependencies 106 = new HashMap<>(); 107 private int resolvedCount; 108 private Runnable onResolved; 109 private Runnable onDissolving; 110 private Consumer<String> onRebound; 111 112 /** 113 * Creates a new resolver that uses the given context. 114 * 115 * @param context the context 116 */ 117 public ServiceResolver(BundleContext context) { 118 this.context = context; 119 } 120 121 /** 122 * Constructor for using the {@code ServiceResolver} as base 123 * class for a {@link BundleActivator}. 124 */ 125 @SuppressWarnings("PMD.UncommentedEmptyConstructor") 126 protected ServiceResolver() { 127 } 128 129 /** 130 * Called by the framework when using the {@code ServiceResolver} as 131 * base class for a bundle activator. 132 * <P> 133 * The implementation sets the {@link #context} attribute and calls 134 * {@link #configure()} and {@link #open()}. 135 * 136 * @param context the context 137 * @throws Exception if a problem occurs 138 */ 139 @Override 140 public void start(BundleContext context) throws Exception { 141 this.context = context; 142 configure(); 143 open(); 144 } 145 146 /** 147 * Configures the {@code ServiceResolver}. Must be overridden 148 * by the derived class when using the {@code ServiceResolver} 149 * as base class for a bundle activator. 150 * The derived class must configure the resolver with calls to 151 * at least one of the {@code addDependency} methods. 152 * <P> 153 * The default implementation does nothing. 154 */ 155 protected void configure() { 156 // Default does nothing. 157 } 158 159 /** 160 * Called when all dependencies have been resolved. Overriding this 161 * method is an alternative to setting a callback with 162 * {@link #setOnResolved(Runnable)}. 163 */ 164 protected void onResolved() { 165 // Default does nothing. 166 } 167 168 /** 169 * Called when the resolver is about to leave the resolved state, 170 * i.e. when one of the mandatory services is going to be unbound. 171 * Overriding this method is an alternative to setting a callback with 172 * {@link #setOnDissolving(Runnable)}. 173 */ 174 protected void onDissolving() { 175 // Default does nothing. 176 } 177 178 /** 179 * Called when the preferred service of a resolved dependency 180 * changes. The change may either be a change of properties 181 * reported by the framework or the replacement of the preferred 182 * service with another service. Overriding this method is an 183 * alternative to setting a callback with 184 * {@link #setOnRebound(Consumer)}. 185 * 186 * @param dependency the dependency that has been rebound 187 */ 188 protected void onRebound(String dependency) { 189 // Default does nothing. 190 } 191 192 /** 193 * Called by the framework when using the {@code ServiceResolver} as 194 * base class for a bundle activator. The implementatoin calls 195 * {@link #close()}. 196 * 197 * @param context the context 198 * @throws Exception if a problem occurs 199 */ 200 @Override 201 public void stop(BundleContext context) throws Exception { 202 close(); 203 } 204 205 /** 206 * Sets the function to called when the resolver has entered the 207 * resolved state, i.e. when all mandatory services have been bound. 208 * 209 * @param onResolved the function 210 * @return the service resolver 211 */ 212 public ServiceResolver setOnResolved(Runnable onResolved) { 213 this.onResolved = onResolved; 214 return this; 215 } 216 217 /** 218 * Sets the function to called when the resolver is about to leave the 219 * resolved state, i.e. when one of the mandatory services is going 220 * to be unbound. 221 * 222 * @param onDissolving the function 223 * @return the service resolver 224 */ 225 public ServiceResolver setOnDissolving(Runnable onDissolving) { 226 this.onDissolving = onDissolving; 227 return this; 228 } 229 230 /** 231 * Sets the function to be called when the preferred service 232 * of a resolved dependency changes. The change may either be 233 * a change of properties reported by the framework or the 234 * replacement of the preferred service with another service. 235 * 236 * @param onRebound the on rebound 237 * @return the service resolver 238 */ 239 public ServiceResolver setOnRebound(Consumer<String> onRebound) { 240 this.onRebound = onRebound; 241 return this; 242 } 243 244 /** 245 * Adds a mandatory dependency on the service specified by the 246 * class. The name of the class is used as name of the 247 * dependency. 248 * 249 * @param clazz the class 250 * @return the service resolver 251 */ 252 public ServiceResolver addDependency(Class<?> clazz) { 253 addDependency(clazz.getName(), clazz.getName()); 254 return this; 255 } 256 257 /** 258 * Adds a mandatory dependency on the service specified by the 259 * filter. 260 * 261 * @param name the name of the dependency 262 * @param filter the filter 263 * @return the service resolver 264 */ 265 @SuppressWarnings("PMD.AvoidDuplicateLiterals") 266 public ServiceResolver addDependency(String name, Filter filter) { 267 synchronized (this) { 268 if (isOpen) { 269 throw new IllegalStateException( 270 "Cannot add dependencies to open reolver."); 271 } 272 dependencies.put(name, new ServiceCollector<>(context, filter) 273 .setOnBound(this::boundCb).setOnUnbinding(this::unbindingCb) 274 .setOnModfied((ref, svc) -> modifiedCb(name))); 275 return this; 276 } 277 } 278 279 /** 280 * Adds a mandatory dependency on the service specified by the 281 * service reference. 282 * 283 * @param name the name of the dependency 284 * @param reference the reference 285 * @return the service resolver 286 */ 287 public ServiceResolver addDependency(String name, 288 ServiceReference<?> reference) { 289 synchronized (this) { 290 if (isOpen) { 291 throw new IllegalStateException( 292 "Cannot add dependencies to open reolver."); 293 } 294 dependencies.put(name, new ServiceCollector<>(context, reference) 295 .setOnBound(this::boundCb).setOnUnbinding(this::unbindingCb) 296 .setOnModfied((ref, svc) -> modifiedCb(name))); 297 return this; 298 } 299 } 300 301 /** 302 * Adds a mandatory dependency on the service specified by the 303 * service reference. 304 * 305 * @param name the name of the dependency 306 * @param className the class name 307 * @return the service resolver 308 */ 309 public ServiceResolver addDependency(String name, String className) { 310 synchronized (this) { 311 if (isOpen) { 312 throw new IllegalStateException( 313 "Cannot add dependencies to open reolver."); 314 } 315 dependencies.put(name, new ServiceCollector<>(context, className) 316 .setOnBound(this::boundCb).setOnUnbinding(this::unbindingCb) 317 .setOnModfied((ref, svc) -> modifiedCb(name))); 318 return this; 319 } 320 } 321 322 /** 323 * Adds an optional dependency on the service specified by the 324 * class. The name of the class is used as identifier for the 325 * dependency. 326 * 327 * @param clazz the class 328 * @return the service resolver 329 */ 330 public ServiceResolver addOptionalDependency(Class<?> clazz) { 331 addOptionalDependency(clazz.getName(), clazz.getName()); 332 return this; 333 } 334 335 /** 336 * Adds an optional dependency on the service specified by the 337 * filter. 338 * 339 * @param name the name of the dependency 340 * @param filter the filter 341 * @return the service resolver 342 */ 343 public ServiceResolver addOptionalDependency(String name, Filter filter) { 344 synchronized (this) { 345 if (isOpen) { 346 throw new IllegalStateException( 347 "Cannot add dependencies to open resolver."); 348 } 349 optDependencies.put(name, new ServiceCollector<>(context, filter) 350 .setOnModfied((ref, svc) -> modifiedCb(name))); 351 return this; 352 } 353 } 354 355 /** 356 * Adds an optional dependency on the service specified by the 357 * service reference. 358 * 359 * @param name the name of the dependency 360 * @param reference the reference 361 * @return the service resolver 362 */ 363 public ServiceResolver addOptionalDependency(String name, 364 ServiceReference<?> reference) { 365 synchronized (this) { 366 if (isOpen) { 367 throw new IllegalStateException( 368 "Cannot add dependencies to open reolver."); 369 } 370 optDependencies.put(name, 371 new ServiceCollector<>(context, reference) 372 .setOnModfied((ref, svc) -> modifiedCb(name))); 373 return this; 374 } 375 } 376 377 /** 378 * Adds an optional dependency on the service specified by the 379 * service reference. 380 * 381 * @param name the name of the dependency 382 * @param className the class name 383 * @return the service resolver 384 */ 385 public ServiceResolver addOptionalDependency(String name, 386 String className) { 387 synchronized (this) { 388 if (isOpen) { 389 throw new IllegalStateException( 390 "Cannot add dependencies to open reolver."); 391 } 392 optDependencies.put(name, 393 new ServiceCollector<>(context, className) 394 .setOnModfied((ref, svc) -> modifiedCb(name))); 395 return this; 396 } 397 } 398 399 @SuppressWarnings("PMD.UnusedFormalParameter") 400 private void boundCb(ServiceReference<?> reference, Object service) { 401 synchronized (this) { 402 resolvedCount += 1; 403 if (resolvedCount == dependencies.size()) { 404 Optional.ofNullable(onResolved).ifPresent(cb -> cb.run()); 405 onResolved(); 406 } 407 } 408 } 409 410 @SuppressWarnings("PMD.UnusedFormalParameter") 411 private void unbindingCb(ServiceReference<?> reference, Object service) { 412 synchronized (this) { 413 if (resolvedCount == dependencies.size()) { 414 Optional.ofNullable(onDissolving).ifPresent(cb -> cb.run()); 415 onDissolving(); 416 } 417 resolvedCount -= 1; 418 } 419 } 420 421 private void modifiedCb(String dependency) { 422 synchronized (this) { 423 Optional.ofNullable(onRebound) 424 .ifPresent(cb -> cb.accept(dependency)); 425 onRebound(dependency); 426 } 427 } 428 429 /** 430 * Starts the resolver. While open, the resolver attempts 431 * to obtain service implementation for all registered 432 * service dependencies. 433 */ 434 public void open() { 435 synchronized (this) { 436 if (isOpen) { 437 return; 438 } 439 resolvedCount = 0; 440 } 441 for (ServiceCollector<?, ?> coll : dependencies.values()) { 442 coll.open(); 443 } 444 for (ServiceCollector<?, ?> coll : optDependencies.values()) { 445 coll.open(); 446 } 447 isOpen = true; 448 } 449 450 /** 451 * Stops the resolver. All maintained services are released. 452 */ 453 public void close() { 454 synchronized (this) { 455 if (!isOpen) { 456 return; 457 } 458 isOpen = false; 459 for (ServiceCollector<?, ?> coll : dependencies.values()) { 460 coll.close(); 461 } 462 for (ServiceCollector<?, ?> coll : optDependencies.values()) { 463 coll.close(); 464 } 465 dependencies.clear(); 466 optDependencies.clear(); 467 } 468 } 469 470 /** 471 * Checks if the resolver is open. 472 * 473 * @return true, if open 474 */ 475 public boolean isOpen() { 476 return isOpen; 477 } 478 479 /** 480 * Checks if the resolver is in the resolved state. 481 * 482 * @return the result 483 */ 484 public boolean isResolved() { 485 synchronized (this) { 486 return resolvedCount == dependencies.size(); 487 } 488 } 489 490 /** 491 * Returns the service found for the mandatory dependency with the 492 * given name. This method should only be called when the resolver 493 * is in state resolved. 494 * 495 * Note that due to the threaded nature of the environment, 496 * this method can return {@code null}, even if the resolver 497 * was in state resolved before its invocation, because the service 498 * can go away between the check and the invocation of this method. 499 * 500 * If you want to be sure that services haven't been unregistered 501 * concurrently, the check for resolved and the invocation of this 502 * method must be in a block synchronized on this resolver. 503 * 504 * Consider using {@link ServiceResolver#with(String, Function)} 505 * or {@link #with(Class, Function)} as an alternative. 506 * 507 * @param <T> the type of the service 508 * @param name the name of the dependency 509 * @param clazz the class of the service 510 * @return the service or {@code null} if called in unresolved 511 * state and the dependency is not resolved 512 */ 513 @SuppressWarnings("unchecked") 514 public <T> T get(String name, Class<T> clazz) { 515 return Optional.ofNullable(dependencies.get(name)) 516 .map(coll -> ((ServiceCollector<?, T>) coll).service().get()) 517 .orElse(null); 518 } 519 520 /** 521 * Returns the service found for the mandatory dependency 522 * using the name of the class as name of the dependency. 523 * 524 * @param <T> the type of the service 525 * @param clazz the class of the service 526 * @return the service or {@code null} if called in unresolved 527 * state and the dependency is not resolved 528 * @see #get(String, Class) 529 */ 530 public <T> T get(Class<T> clazz) { 531 return get(clazz.getName(), clazz); 532 } 533 534 /** 535 * Returns the service found for the optional dependency 536 * with the given name, if it exists. 537 * 538 * @param <T> the type of the service 539 * @param name the name of the dependency 540 * @param clazz the class of the service 541 * @return the result 542 */ 543 @SuppressWarnings("unchecked") 544 public <T> Optional<T> optional(String name, Class<T> clazz) { 545 return Optional.ofNullable(optDependencies.get(name)) 546 .map(coll -> ((ServiceCollector<?, T>) coll).service()) 547 .orElse(Optional.empty()); 548 } 549 550 /** 551 * Returns the service found for the optional dependency 552 * using the name of the class as name of the dependency, 553 * if it exists. 554 * 555 * @param <T> the type of the service 556 * @param clazz the class of the service 557 * @return the result 558 */ 559 public <T> Optional<T> optional(Class<T> clazz) { 560 return optional(clazz.getName(), clazz); 561 } 562 563 /** 564 * Convenience method to invoke the a function with the 565 * service registered as mandatory or optional dependency 566 * while holding a lock on the underlying service collector. 567 * 568 * @param <T> the type of the service 569 * @param <R> the result type 570 * @param name the name of the dependency 571 * @param function the function to invoke with the service as argument 572 * @return the result or {@link Optional#empty()} of the service 573 * was not available. 574 */ 575 @SuppressWarnings("unchecked") 576 public <T, R> Optional<R> with(String name, 577 Function<T, ? extends R> function) { 578 return Optional.ofNullable( 579 dependencies.getOrDefault(name, optDependencies.get(name))) 580 .flatMap( 581 coll -> ((ServiceCollector<?, T>) coll).withService(function)); 582 } 583 584 /** 585 * Convenience method to invoke the a function with the 586 * service registered as mandatory or optional dependency 587 * while holding a lock on the underlying service collector. 588 * 589 * @param <T> the type of the service 590 * @param <R> the result type 591 * @param clazz the class of the service 592 * @param function the function to invoke with the service as argument 593 * @return the result or {@link Optional#empty()} of the service 594 * was not available. 595 */ 596 public <T, R> Optional<R> with(Class<T> clazz, 597 Function<T, ? extends R> function) { 598 return with(clazz.getName(), function); 599 } 600 601}