ContextAwareServiceCollectionResolverImpl.java

  1. /*
  2.  * #%L
  3.  * wcm.io
  4.  * %%
  5.  * Copyright (C) 2022 wcm.io
  6.  * %%
  7.  * Licensed under the Apache License, Version 2.0 (the "License");
  8.  * you may not use this file except in compliance with the License.
  9.  * You may obtain a copy of the License at
  10.  *
  11.  *      http://www.apache.org/licenses/LICENSE-2.0
  12.  *
  13.  * Unless required by applicable law or agreed to in writing, software
  14.  * distributed under the License is distributed on an "AS IS" BASIS,
  15.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  16.  * See the License for the specific language governing permissions and
  17.  * limitations under the License.
  18.  * #L%
  19.  */
  20. package io.wcm.sling.commons.caservice.impl;

  21. import java.util.Collection;
  22. import java.util.concurrent.TimeUnit;
  23. import java.util.function.BiFunction;
  24. import java.util.stream.Stream;

  25. import org.apache.sling.api.adapter.Adaptable;
  26. import org.jetbrains.annotations.NotNull;
  27. import org.jetbrains.annotations.Nullable;
  28. import org.osgi.framework.BundleContext;
  29. import org.osgi.framework.ServiceReference;
  30. import org.slf4j.Logger;
  31. import org.slf4j.LoggerFactory;

  32. import com.github.benmanes.caffeine.cache.Caffeine;
  33. import com.github.benmanes.caffeine.cache.LoadingCache;
  34. import com.github.benmanes.caffeine.cache.RemovalCause;

  35. import io.wcm.sling.commons.caservice.ContextAwareService;
  36. import io.wcm.sling.commons.caservice.ContextAwareServiceCollectionResolver;

  37. class ContextAwareServiceCollectionResolverImpl<S extends ContextAwareService, D>
  38.     implements ContextAwareServiceCollectionResolver<S, D> {

  39.   private final Collection<ServiceReference<S>> serviceReferenceCollection;
  40.   private final ResourcePathResolver resourcePathResolver;

  41.   // cache of service trackers for each SPI interface
  42.   private final LoadingCache<ServiceReference<S>, CollectionItemDecoration<S, D>> decorationCache;

  43.   private static final Logger log = LoggerFactory.getLogger(ContextAwareServiceCollectionResolverImpl.class);

  44.   ContextAwareServiceCollectionResolverImpl(@NotNull Collection<ServiceReference<S>> serviceReferenceCollection,
  45.       @NotNull BiFunction<@NotNull ServiceReference<S>, @Nullable S, @Nullable D> decorator,
  46.       @NotNull ResourcePathResolver resourcePathResolver,
  47.       @NotNull BundleContext bundleContext) {
  48.     this.serviceReferenceCollection = serviceReferenceCollection;
  49.     this.resourcePathResolver = resourcePathResolver;
  50.     this.decorationCache = buildCache(decorator, bundleContext);
  51.   }

  52.   private static <S extends ContextAwareService, D> LoadingCache<ServiceReference<S>, CollectionItemDecoration<S, D>> buildCache(
  53.       @NotNull BiFunction<@NotNull ServiceReference<S>, @Nullable S, @Nullable D> decorator, @NotNull BundleContext bundleContext) {
  54.     return Caffeine.newBuilder()
  55.         // expire cached entry after 24h
  56.         .expireAfterAccess(24, TimeUnit.HOURS)
  57.         // unget service on removal
  58.         .removalListener((ServiceReference<S> key, CollectionItemDecoration<S, D> value, RemovalCause cause) -> {
  59.           log.debug("Remove service {}", value);
  60.           bundleContext.ungetService(key);
  61.         })
  62.         // build cache lazily
  63.         .build((ServiceReference<S> serviceReference) -> {
  64.           CollectionItemDecoration<S, D> item = new CollectionItemDecoration<>(serviceReference, decorator, bundleContext);
  65.           log.debug("Add service {}", item);
  66.           return item;
  67.         });
  68.   }

  69.   @Override
  70.   public @Nullable S resolve(@Nullable Adaptable adaptable) {
  71.     return getMatching(adaptable)
  72.         .map(CollectionItemDecoration::getService)
  73.         .findFirst().orElse(null);
  74.   }

  75.   @Override
  76.   @SuppressWarnings("null")
  77.   public @NotNull Stream<S> resolveAll(@Nullable Adaptable adaptable) {
  78.     return getMatching(adaptable)
  79.         .map(CollectionItemDecoration::getService);
  80.   }

  81.   @Override
  82.   public @Nullable D resolveDecorated(@Nullable Adaptable adaptable) {
  83.     return getMatching(adaptable)
  84.         .map(CollectionItemDecoration::getDecoration)
  85.         .findFirst().orElse(null);
  86.   }

  87.   @Override
  88.   @SuppressWarnings("null")
  89.   public @NotNull Stream<D> resolveAllDecorated(@Nullable Adaptable adaptable) {
  90.     return getMatching(adaptable)
  91.         .map(CollectionItemDecoration::getDecoration);
  92.   }

  93.   private @NotNull Stream<CollectionItemDecoration<S, D>> getMatching(@Nullable Adaptable adaptable) {
  94.     String resourcePath = resourcePathResolver.get(adaptable);
  95.     return serviceReferenceCollection.stream()
  96.         .map(decorationCache::get)
  97.         .filter(CollectionItemDecoration::isValid)
  98.         .filter(item -> item.matches(resourcePath));
  99.   }

  100.   @Override
  101.   public void close() {
  102.     this.decorationCache.invalidateAll();
  103.   }

  104. }