All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.gerrit.extensions.registration.DynamicItem Maven / Gradle / Ivy

The newest version!
// Copyright (C) 2012 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package com.google.gerrit.extensions.registration;

import static java.lang.annotation.RetentionPolicy.RUNTIME;

import com.google.errorprone.annotations.CanIgnoreReturnValue;
import com.google.gerrit.common.Nullable;
import com.google.inject.Binder;
import com.google.inject.BindingAnnotation;
import com.google.inject.Key;
import com.google.inject.Provider;
import com.google.inject.ProvisionException;
import com.google.inject.Scopes;
import com.google.inject.TypeLiteral;
import com.google.inject.binder.LinkedBindingBuilder;
import com.google.inject.util.Providers;
import com.google.inject.util.Types;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import java.util.concurrent.atomic.AtomicReference;

/**
 * A single item that can be modified as plugins reload.
 *
 * 

DynamicItems are always mapped as singletons in Guice. Items store a Provider internally, and * resolve the provider to an instance on demand. This enables registrations to decide between * singleton and non-singleton members. If multiple plugins try to provide the same Provider, an * exception is thrown. */ public class DynamicItem { /** Annotate a DynamicItem to be final and being bound at most once. */ @Target({ElementType.TYPE}) @Retention(RUNTIME) @BindingAnnotation public @interface Final { String implementedByPlugin() default ""; } /** * Declare a singleton {@code DynamicItem} with a binder. * *

Items must be defined in a Guice module before they can be bound: * *

   *   DynamicItem.itemOf(binder(), Interface.class);
   *   DynamicItem.bind(binder(), Interface.class).to(Impl.class);
   * 
* * @param binder a new binder created in the module. * @param member type of entry to store. */ public static void itemOf(Binder binder, Class member) { itemOf(binder, TypeLiteral.get(member)); } /** * Declare a singleton {@code DynamicItem} with a binder. * *

Items must be defined in a Guice module before they can be bound: * *

   *   DynamicSet.itemOf(binder(), new TypeLiteral<Thing<Foo>>() {});
   * 
* * @param binder a new binder created in the module. * @param member type of entry to store. */ public static void itemOf(Binder binder, TypeLiteral member) { Key> key = keyFor(member); binder.bind(key).toProvider(new DynamicItemProvider<>(member, key)).in(Scopes.SINGLETON); } /** * Construct a single {@code DynamicItem} with a fixed value. * *

Primarily useful for passing {@code DynamicItem}s to constructors in tests. * * @param member type of item. * @param item item to store. */ public static DynamicItem itemOf(Class member, T item) { return new DynamicItem<>( keyFor(TypeLiteral.get(member)), Providers.of(item), PluginName.GERRIT); } @SuppressWarnings("unchecked") private static Key> keyFor(TypeLiteral member) { return (Key>) Key.get(Types.newParameterizedType(DynamicItem.class, member.getType())); } /** * Bind one implementation as the item using a unique annotation. * * @param binder a new binder created in the module. * @param type type of entry to store. * @return a binder to continue configuring the new item. */ public static LinkedBindingBuilder bind(Binder binder, Class type) { return bind(binder, TypeLiteral.get(type)); } /** * Bind one implementation as the item. * * @param binder a new binder created in the module. * @param type type of entry to store. * @return a binder to continue configuring the new item. */ public static LinkedBindingBuilder bind(Binder binder, TypeLiteral type) { return binder.bind(type); } private final Key> key; private final AtomicReference> ref; DynamicItem(Key> key, Provider provider, String pluginName) { Extension in = null; if (provider != null) { in = new Extension<>(pluginName, provider); } this.key = key; this.ref = new AtomicReference<>(in); } @Nullable public Extension getEntry() { return ref.get(); } /** * Get the configured item, or null. * * @return the configured item instance; null if no implementation has been bound to the item. * This is common if no plugin registered an implementation for the type. */ @Nullable public T get() { Extension item = ref.get(); return item != null ? item.get() : null; } /** * Get the name of the plugin that has bound the configured item, or null. * * @return the name of the plugin that has bound the configured item; null if no implementation * has been bound to the item. This is common if no plugin registered an implementation for * the type. */ @Nullable public String getPluginName() { Extension item = ref.get(); return item != null ? item.getPluginName() : null; } /** * Set the element to provide. * * @param item the item to use. Must not be null. * @param pluginName the name of the plugin providing the item. * @return handle to remove the item at a later point in time. */ @CanIgnoreReturnValue public RegistrationHandle set(T item, String pluginName) { return set(Providers.of(item), pluginName); } /** * Set the element to provide. * * @param impl the item to add to the collection. Must not be null. * @param pluginName name of the source providing the implementation. * @return handle to remove the item at a later point in time. */ @CanIgnoreReturnValue public RegistrationHandle set(Provider impl, String pluginName) { final Extension item = new Extension<>(pluginName, impl); Extension old = null; while (!ref.compareAndSet(old, item)) { old = ref.get(); if (old != null && !PluginName.GERRIT.equals(old.getPluginName())) { throw new ProvisionException( String.format( "%s already provided by %s, ignoring plugin %s", key.getTypeLiteral(), old.getPluginName(), pluginName)); } } final Extension defaultItem = old; return () -> ref.compareAndSet(item, defaultItem); } /** * Set the element that may be hot-replaceable in the future. * * @param key unique description from the item's Guice binding. This can be later obtained from * the registration handle to facilitate matching with the new equivalent instance during a * hot reload. * @param impl the item to set as our value right now. Must not be null. * @param pluginName the name of the plugin providing the item. * @return a handle that can remove this item later, or hot-swap the item. */ @CanIgnoreReturnValue public ReloadableRegistrationHandle set(Key key, Provider impl, String pluginName) { final Extension item = new Extension<>(pluginName, impl); Extension old = null; while (!ref.compareAndSet(old, item)) { old = ref.get(); if (old != null && !PluginName.GERRIT.equals(old.getPluginName()) && !pluginName.equals(old.getPluginName())) { // We allow to replace: // 1. Gerrit core items, e.g. websession cache // can be replaced by plugin implementation // 2. Reload of current plugin throw new ProvisionException( String.format( "%s already provided by %s, ignoring plugin %s", this.key.getTypeLiteral(), old.getPluginName(), pluginName)); } } return new ReloadableHandle(key, item, old); } private class ReloadableHandle implements ReloadableRegistrationHandle { private final Key handleKey; private final Extension item; private final Extension defaultItem; ReloadableHandle(Key handleKey, Extension item, Extension defaultItem) { this.handleKey = handleKey; this.item = item; this.defaultItem = defaultItem; } @Override public Key getKey() { return handleKey; } @Override public void remove() { ref.compareAndSet(item, defaultItem); } @Override @Nullable public ReloadableHandle replace(Key newKey, Provider newItem) { Extension n = new Extension<>(item.getPluginName(), newItem); if (ref.compareAndSet(item, n)) { return new ReloadableHandle(newKey, n, defaultItem); } return null; } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy