LCOV - code coverage report
Current view: top level - extensions/registration - DynamicItem.java (source / functions) Hit Total Coverage
Test: _coverage_report.dat Lines: 19 57 33.3 %
Date: 2022-11-19 15:00:39 Functions: 8 18 44.4 %

          Line data    Source code
       1             : // Copyright (C) 2012 The Android Open Source Project
       2             : //
       3             : // Licensed under the Apache License, Version 2.0 (the "License");
       4             : // you may not use this file except in compliance with the License.
       5             : // You may obtain a copy of the License at
       6             : //
       7             : // http://www.apache.org/licenses/LICENSE-2.0
       8             : //
       9             : // Unless required by applicable law or agreed to in writing, software
      10             : // distributed under the License is distributed on an "AS IS" BASIS,
      11             : // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
      12             : // See the License for the specific language governing permissions and
      13             : // limitations under the License.
      14             : 
      15             : package com.google.gerrit.extensions.registration;
      16             : 
      17             : import com.google.gerrit.common.Nullable;
      18             : import com.google.inject.Binder;
      19             : import com.google.inject.Key;
      20             : import com.google.inject.Provider;
      21             : import com.google.inject.ProvisionException;
      22             : import com.google.inject.Scopes;
      23             : import com.google.inject.TypeLiteral;
      24             : import com.google.inject.binder.LinkedBindingBuilder;
      25             : import com.google.inject.util.Providers;
      26             : import com.google.inject.util.Types;
      27             : import java.util.concurrent.atomic.AtomicReference;
      28             : 
      29             : /**
      30             :  * A single item that can be modified as plugins reload.
      31             :  *
      32             :  * <p>DynamicItems are always mapped as singletons in Guice. Items store a Provider internally, and
      33             :  * resolve the provider to an instance on demand. This enables registrations to decide between
      34             :  * singleton and non-singleton members. If multiple plugins try to provide the same Provider, an
      35             :  * exception is thrown.
      36             :  */
      37             : public class DynamicItem<T> {
      38             :   /**
      39             :    * Declare a singleton {@code DynamicItem<T>} with a binder.
      40             :    *
      41             :    * <p>Items must be defined in a Guice module before they can be bound:
      42             :    *
      43             :    * <pre>
      44             :    *   DynamicItem.itemOf(binder(), Interface.class);
      45             :    *   DynamicItem.bind(binder(), Interface.class).to(Impl.class);
      46             :    * </pre>
      47             :    *
      48             :    * @param binder a new binder created in the module.
      49             :    * @param member type of entry to store.
      50             :    */
      51             :   public static <T> void itemOf(Binder binder, Class<T> member) {
      52         152 :     itemOf(binder, TypeLiteral.get(member));
      53         152 :   }
      54             : 
      55             :   /**
      56             :    * Declare a singleton {@code DynamicItem<T>} with a binder.
      57             :    *
      58             :    * <p>Items must be defined in a Guice module before they can be bound:
      59             :    *
      60             :    * <pre>
      61             :    *   DynamicSet.itemOf(binder(), new TypeLiteral&lt;Thing&lt;Foo&gt;&gt;() {});
      62             :    * </pre>
      63             :    *
      64             :    * @param binder a new binder created in the module.
      65             :    * @param member type of entry to store.
      66             :    */
      67             :   public static <T> void itemOf(Binder binder, TypeLiteral<T> member) {
      68         152 :     Key<DynamicItem<T>> key = keyFor(member);
      69         152 :     binder.bind(key).toProvider(new DynamicItemProvider<>(member, key)).in(Scopes.SINGLETON);
      70         152 :   }
      71             : 
      72             :   /**
      73             :    * Construct a single {@code DynamicItem<T>} with a fixed value.
      74             :    *
      75             :    * <p>Primarily useful for passing {@code DynamicItem}s to constructors in tests.
      76             :    *
      77             :    * @param member type of item.
      78             :    * @param item item to store.
      79             :    */
      80             :   public static <T> DynamicItem<T> itemOf(Class<T> member, T item) {
      81           0 :     return new DynamicItem<>(
      82           0 :         keyFor(TypeLiteral.get(member)), Providers.of(item), PluginName.GERRIT);
      83             :   }
      84             : 
      85             :   @SuppressWarnings("unchecked")
      86             :   private static <T> Key<DynamicItem<T>> keyFor(TypeLiteral<T> member) {
      87         152 :     return (Key<DynamicItem<T>>)
      88         152 :         Key.get(Types.newParameterizedType(DynamicItem.class, member.getType()));
      89             :   }
      90             : 
      91             :   /**
      92             :    * Bind one implementation as the item using a unique annotation.
      93             :    *
      94             :    * @param binder a new binder created in the module.
      95             :    * @param type type of entry to store.
      96             :    * @return a binder to continue configuring the new item.
      97             :    */
      98             :   public static <T> LinkedBindingBuilder<T> bind(Binder binder, Class<T> type) {
      99         152 :     return bind(binder, TypeLiteral.get(type));
     100             :   }
     101             : 
     102             :   /**
     103             :    * Bind one implementation as the item.
     104             :    *
     105             :    * @param binder a new binder created in the module.
     106             :    * @param type type of entry to store.
     107             :    * @return a binder to continue configuring the new item.
     108             :    */
     109             :   public static <T> LinkedBindingBuilder<T> bind(Binder binder, TypeLiteral<T> type) {
     110         152 :     return binder.bind(type);
     111             :   }
     112             : 
     113             :   private final Key<DynamicItem<T>> key;
     114             :   private final AtomicReference<Extension<T>> ref;
     115             : 
     116         151 :   DynamicItem(Key<DynamicItem<T>> key, Provider<T> provider, String pluginName) {
     117         151 :     Extension<T> in = null;
     118         151 :     if (provider != null) {
     119         151 :       in = new Extension<>(pluginName, provider);
     120             :     }
     121         151 :     this.key = key;
     122         151 :     this.ref = new AtomicReference<>(in);
     123         151 :   }
     124             : 
     125             :   @Nullable
     126             :   public Extension<T> getEntry() {
     127         146 :     return ref.get();
     128             :   }
     129             : 
     130             :   /**
     131             :    * Get the configured item, or null.
     132             :    *
     133             :    * @return the configured item instance; null if no implementation has been bound to the item.
     134             :    *     This is common if no plugin registered an implementation for the type.
     135             :    */
     136             :   @Nullable
     137             :   public T get() {
     138         140 :     Extension<T> item = ref.get();
     139         129 :     return item != null ? item.get() : null;
     140             :   }
     141             : 
     142             :   /**
     143             :    * Get the name of the plugin that has bound the configured item, or null.
     144             :    *
     145             :    * @return the name of the plugin that has bound the configured item; null if no implementation
     146             :    *     has been bound to the item. This is common if no plugin registered an implementation for
     147             :    *     the type.
     148             :    */
     149             :   @Nullable
     150             :   public String getPluginName() {
     151           0 :     Extension<T> item = ref.get();
     152           0 :     return item != null ? item.getPluginName() : null;
     153             :   }
     154             : 
     155             :   /**
     156             :    * Set the element to provide.
     157             :    *
     158             :    * @param item the item to use. Must not be null.
     159             :    * @param pluginName the name of the plugin providing the item.
     160             :    * @return handle to remove the item at a later point in time.
     161             :    */
     162             :   public RegistrationHandle set(T item, String pluginName) {
     163           0 :     return set(Providers.of(item), pluginName);
     164             :   }
     165             : 
     166             :   /**
     167             :    * Set the element to provide.
     168             :    *
     169             :    * @param impl the item to add to the collection. Must not be null.
     170             :    * @param pluginName name of the source providing the implementation.
     171             :    * @return handle to remove the item at a later point in time.
     172             :    */
     173             :   public RegistrationHandle set(Provider<T> impl, String pluginName) {
     174           0 :     final Extension<T> item = new Extension<>(pluginName, impl);
     175           0 :     Extension<T> old = null;
     176           0 :     while (!ref.compareAndSet(old, item)) {
     177           0 :       old = ref.get();
     178           0 :       if (old != null && !PluginName.GERRIT.equals(old.getPluginName())) {
     179           0 :         throw new ProvisionException(
     180           0 :             String.format(
     181             :                 "%s already provided by %s, ignoring plugin %s",
     182           0 :                 key.getTypeLiteral(), old.getPluginName(), pluginName));
     183             :       }
     184             :     }
     185             : 
     186           0 :     final Extension<T> defaultItem = old;
     187           0 :     return () -> ref.compareAndSet(item, defaultItem);
     188             :   }
     189             : 
     190             :   /**
     191             :    * Set the element that may be hot-replaceable in the future.
     192             :    *
     193             :    * @param key unique description from the item's Guice binding. This can be later obtained from
     194             :    *     the registration handle to facilitate matching with the new equivalent instance during a
     195             :    *     hot reload.
     196             :    * @param impl the item to set as our value right now. Must not be null.
     197             :    * @param pluginName the name of the plugin providing the item.
     198             :    * @return a handle that can remove this item later, or hot-swap the item.
     199             :    */
     200             :   public ReloadableRegistrationHandle<T> set(Key<T> key, Provider<T> impl, String pluginName) {
     201           0 :     final Extension<T> item = new Extension<>(pluginName, impl);
     202           0 :     Extension<T> old = null;
     203           0 :     while (!ref.compareAndSet(old, item)) {
     204           0 :       old = ref.get();
     205           0 :       if (old != null
     206           0 :           && !PluginName.GERRIT.equals(old.getPluginName())
     207           0 :           && !pluginName.equals(old.getPluginName())) {
     208             :         // We allow to replace:
     209             :         // 1. Gerrit core items, e.g. websession cache
     210             :         //    can be replaced by plugin implementation
     211             :         // 2. Reload of current plugin
     212           0 :         throw new ProvisionException(
     213           0 :             String.format(
     214             :                 "%s already provided by %s, ignoring plugin %s",
     215           0 :                 this.key.getTypeLiteral(), old.getPluginName(), pluginName));
     216             :       }
     217             :     }
     218           0 :     return new ReloadableHandle(key, item, old);
     219             :   }
     220             : 
     221             :   private class ReloadableHandle implements ReloadableRegistrationHandle<T> {
     222             :     private final Key<T> handleKey;
     223             :     private final Extension<T> item;
     224             :     private final Extension<T> defaultItem;
     225             : 
     226           0 :     ReloadableHandle(Key<T> handleKey, Extension<T> item, Extension<T> defaultItem) {
     227           0 :       this.handleKey = handleKey;
     228           0 :       this.item = item;
     229           0 :       this.defaultItem = defaultItem;
     230           0 :     }
     231             : 
     232             :     @Override
     233             :     public Key<T> getKey() {
     234           0 :       return handleKey;
     235             :     }
     236             : 
     237             :     @Override
     238             :     public void remove() {
     239           0 :       ref.compareAndSet(item, defaultItem);
     240           0 :     }
     241             : 
     242             :     @Override
     243             :     @Nullable
     244             :     public ReloadableHandle replace(Key<T> newKey, Provider<T> newItem) {
     245           0 :       Extension<T> n = new Extension<>(item.getPluginName(), newItem);
     246           0 :       if (ref.compareAndSet(item, n)) {
     247           0 :         return new ReloadableHandle(newKey, n, defaultItem);
     248             :       }
     249           0 :       return null;
     250             :     }
     251             :   }
     252             : }

Generated by: LCOV version 1.16+git.20220603.dfeb750