diff options
Diffstat (limited to 'gerrit-extension-api/src/main/java/com/google/gerrit/extensions/registration/DynamicItem.java')
-rw-r--r-- | gerrit-extension-api/src/main/java/com/google/gerrit/extensions/registration/DynamicItem.java | 220 |
1 files changed, 220 insertions, 0 deletions
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/registration/DynamicItem.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/registration/DynamicItem.java new file mode 100644 index 0000000000..aa1dc76126 --- /dev/null +++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/registration/DynamicItem.java @@ -0,0 +1,220 @@ +// 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 com.google.inject.Binder; +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.util.concurrent.atomic.AtomicReference; + +/** + * A single item that can be modified as plugins reload. + * <p> + * 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<T> { + /** Pair of provider implementation and plugin providing it. */ + static class NamedProvider<T> { + final Provider<T> impl; + final String pluginName; + + NamedProvider(Provider<T> provider, String pluginName) { + this.impl = provider; + this.pluginName = pluginName; + } + } + + /** + * Declare a singleton {@code DynamicItem<T>} with a binder. + * <p> + * Items must be defined in a Guice module before they can be bound: + * <pre> + * DynamicItem.itemOf(binder(), Interface.class); + * DynamicItem.bind(binder(), Interface.class).to(Impl.class); + * </pre> + * + * @param binder a new binder created in the module. + * @param member type of entry to store. + */ + public static <T> void itemOf(Binder binder, Class<T> member) { + itemOf(binder, TypeLiteral.get(member)); + } + + /** + * Declare a singleton {@code DynamicItem<T>} with a binder. + * <p> + * Items must be defined in a Guice module before they can be bound: + * <pre> + * DynamicSet.itemOf(binder(), new TypeLiteral<Thing<Foo>>() {}); + * </pre> + * + * @param binder a new binder created in the module. + * @param member type of entry to store. + */ + public static <T> void itemOf(Binder binder, TypeLiteral<T> member) { + @SuppressWarnings("unchecked") + Key<DynamicItem<T>> key = (Key<DynamicItem<T>>) Key.get( + Types.newParameterizedType(DynamicItem.class, member.getType())); + binder.bind(key) + .toProvider(new DynamicItemProvider<T>(member, key)) + .in(Scopes.SINGLETON); + } + + /** + * 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 <T> LinkedBindingBuilder<T> bind(Binder binder, Class<T> 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 <T> LinkedBindingBuilder<T> bind(Binder binder, + TypeLiteral<T> type) { + return binder.bind(type); + } + + private final Key<DynamicItem<T>> key; + private final AtomicReference<NamedProvider<T>> ref; + + DynamicItem(Key<DynamicItem<T>> key, Provider<T> provider, String pluginName) { + NamedProvider<T> in = null; + if (provider != null) { + in = new NamedProvider<T>(provider, pluginName); + } + this.key = key; + this.ref = new AtomicReference<NamedProvider<T>>(in); + } + + /** + * 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. + */ + public T get() { + NamedProvider<T> item = ref.get(); + return item != null ? item.impl.get() : 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. + */ + 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. + */ + public RegistrationHandle set(Provider<T> impl, String pluginName) { + final NamedProvider<T> item = new NamedProvider<T>(impl, pluginName); + while (!ref.compareAndSet(null, item)) { + NamedProvider<T> old = ref.get(); + if (old != null) { + throw new ProvisionException(String.format( + "%s already provided by %s, ignoring plugin %s", + key.getTypeLiteral(), old.pluginName, pluginName)); + } + } + return new RegistrationHandle() { + @Override + public void remove() { + ref.compareAndSet(item, null); + } + }; + } + + /** + * 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. + */ + public ReloadableRegistrationHandle<T> set(Key<T> key, Provider<T> impl, + String pluginName) { + final NamedProvider<T> item = new NamedProvider<T>(impl, pluginName); + while (!ref.compareAndSet(null, item)) { + NamedProvider<T> old = ref.get(); + if (old != null) { + throw new ProvisionException(String.format( + "%s already provided by %s, ignoring plugin %s", + this.key.getTypeLiteral(), old.pluginName, pluginName)); + } + } + return new ReloadableHandle(key, item); + } + + private class ReloadableHandle implements ReloadableRegistrationHandle<T> { + private final Key<T> key; + private final NamedProvider<T> item; + + ReloadableHandle(Key<T> key, NamedProvider<T> item) { + this.key = key; + this.item = item; + } + + @Override + public Key<T> getKey() { + return key; + } + + @Override + public void remove() { + ref.compareAndSet(item, null); + } + + @Override + public ReloadableHandle replace(Key<T> newKey, Provider<T> newItem) { + NamedProvider<T> n = new NamedProvider<T>(newItem, item.pluginName); + if (ref.compareAndSet(item, n)) { + return new ReloadableHandle(newKey, n); + } + return null; + } + } +} |