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<Thing<Foo>>() {});
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 : }
|