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.server.plugins;
16 :
17 : import static com.google.gerrit.extensions.webui.JavaScriptPlugin.STATIC_INIT_JS;
18 : import static com.google.gerrit.server.plugins.AutoRegisterUtil.calculateBindAnnotation;
19 : import static com.google.gerrit.server.plugins.PluginGuiceEnvironment.is;
20 :
21 : import com.google.common.collect.LinkedListMultimap;
22 : import com.google.common.collect.ListMultimap;
23 : import com.google.common.flogger.FluentLogger;
24 : import com.google.gerrit.extensions.annotations.Export;
25 : import com.google.gerrit.extensions.annotations.ExtensionPoint;
26 : import com.google.gerrit.extensions.annotations.Listen;
27 : import com.google.gerrit.extensions.registration.DynamicSet;
28 : import com.google.gerrit.extensions.webui.JavaScriptPlugin;
29 : import com.google.gerrit.extensions.webui.WebUiPlugin;
30 : import com.google.gerrit.server.plugins.PluginContentScanner.ExtensionMetaData;
31 : import com.google.inject.AbstractModule;
32 : import com.google.inject.Module;
33 : import com.google.inject.Scopes;
34 : import com.google.inject.TypeLiteral;
35 : import java.io.IOException;
36 : import java.lang.annotation.Annotation;
37 : import java.lang.reflect.ParameterizedType;
38 : import java.util.Arrays;
39 : import java.util.HashSet;
40 : import java.util.Map;
41 : import java.util.Set;
42 :
43 : class AutoRegisterModules {
44 2 : private static final FluentLogger logger = FluentLogger.forEnclosingClass();
45 :
46 : private final String pluginName;
47 : private final PluginGuiceEnvironment env;
48 : private final PluginContentScanner scanner;
49 : private final ClassLoader classLoader;
50 : private final ModuleGenerator sshGen;
51 : private final ModuleGenerator httpGen;
52 :
53 : private Set<Class<?>> sysSingletons;
54 : private ListMultimap<TypeLiteral<?>, Class<?>> sysListen;
55 : private String initJs;
56 :
57 : Module sysModule;
58 : Module sshModule;
59 : Module httpModule;
60 :
61 : AutoRegisterModules(
62 : String pluginName,
63 : PluginGuiceEnvironment env,
64 : PluginContentScanner scanner,
65 2 : ClassLoader classLoader) {
66 2 : this.pluginName = pluginName;
67 2 : this.env = env;
68 2 : this.scanner = scanner;
69 2 : this.classLoader = classLoader;
70 2 : this.sshGen = env.hasSshModule() ? env.newSshModuleGenerator() : new ModuleGenerator.NOP();
71 2 : this.httpGen = env.hasHttpModule() ? env.newHttpModuleGenerator() : new ModuleGenerator.NOP();
72 2 : }
73 :
74 : AutoRegisterModules discover() throws InvalidPluginException {
75 2 : sysSingletons = new HashSet<>();
76 2 : sysListen = LinkedListMultimap.create();
77 2 : initJs = null;
78 :
79 2 : sshGen.setPluginName(pluginName);
80 2 : httpGen.setPluginName(pluginName);
81 :
82 2 : scan();
83 :
84 2 : if (!sysSingletons.isEmpty() || !sysListen.isEmpty() || initJs != null) {
85 0 : sysModule = makeSystemModule();
86 : }
87 2 : sshModule = sshGen.create();
88 2 : httpModule = httpGen.create();
89 2 : return this;
90 : }
91 :
92 : private Module makeSystemModule() {
93 0 : return new AbstractModule() {
94 : @Override
95 : protected void configure() {
96 0 : for (Class<?> clazz : sysSingletons) {
97 0 : bind(clazz).in(Scopes.SINGLETON);
98 0 : }
99 0 : for (Map.Entry<TypeLiteral<?>, Class<?>> e : sysListen.entries()) {
100 : @SuppressWarnings("unchecked")
101 0 : TypeLiteral<Object> type = (TypeLiteral<Object>) e.getKey();
102 :
103 : @SuppressWarnings("unchecked")
104 0 : Class<Object> impl = (Class<Object>) e.getValue();
105 :
106 0 : Annotation n = calculateBindAnnotation(impl);
107 0 : bind(type).annotatedWith(n).to(impl);
108 0 : }
109 0 : if (initJs != null) {
110 0 : DynamicSet.bind(binder(), WebUiPlugin.class).toInstance(new JavaScriptPlugin(initJs));
111 : }
112 0 : }
113 : };
114 : }
115 :
116 : private void scan() throws InvalidPluginException {
117 2 : Map<Class<? extends Annotation>, Iterable<ExtensionMetaData>> extensions =
118 2 : scanner.scan(pluginName, Arrays.asList(Export.class, Listen.class));
119 2 : for (ExtensionMetaData export : extensions.get(Export.class)) {
120 1 : export(export);
121 1 : }
122 2 : for (ExtensionMetaData listener : extensions.get(Listen.class)) {
123 0 : listen(listener);
124 0 : }
125 2 : if (env.hasHttpModule()) {
126 0 : exportInitJs();
127 : }
128 2 : }
129 :
130 : private void exportInitJs() {
131 : try {
132 0 : if (scanner.getEntry(STATIC_INIT_JS).isPresent()) {
133 0 : initJs = STATIC_INIT_JS;
134 : }
135 0 : } catch (IOException e) {
136 0 : logger.atWarning().withCause(e).log(
137 : "Cannot access %s from plugin %s: "
138 : + "JavaScript auto-discovered plugin will not be registered",
139 : STATIC_INIT_JS, pluginName);
140 0 : }
141 0 : }
142 :
143 : private void export(ExtensionMetaData def) throws InvalidPluginException {
144 : Class<?> clazz;
145 : try {
146 1 : clazz = Class.forName(def.className, false, classLoader);
147 0 : } catch (ClassNotFoundException err) {
148 0 : throw new InvalidPluginException(
149 0 : String.format("Cannot load %s with @Export(\"%s\")", def.className, def.annotationValue),
150 : err);
151 1 : }
152 :
153 1 : Export export = clazz.getAnnotation(Export.class);
154 1 : if (export == null) {
155 0 : logger.atWarning().log(
156 : "In plugin %s asm incorrectly parsed %s with @Export(\"%s\")",
157 0 : pluginName, clazz.getName(), def.annotationValue);
158 0 : return;
159 : }
160 :
161 1 : if (is("org.apache.sshd.server.command.Command", clazz)) {
162 1 : sshGen.export(export, clazz);
163 0 : } else if (is("javax.servlet.http.HttpServlet", clazz)) {
164 0 : httpGen.export(export, clazz);
165 0 : listen(clazz, clazz);
166 : } else {
167 0 : int cnt = sysListen.size();
168 0 : listen(clazz, clazz);
169 0 : if (cnt == sysListen.size()) {
170 : // If no bindings were recorded, the extension isn't recognized.
171 0 : throw new InvalidPluginException(
172 0 : String.format(
173 0 : "Class %s with @Export(\"%s\") not supported", clazz.getName(), export.value()));
174 : }
175 : }
176 1 : }
177 :
178 : private void listen(ExtensionMetaData def) throws InvalidPluginException {
179 : Class<?> clazz;
180 : try {
181 0 : clazz = Class.forName(def.className, false, classLoader);
182 0 : } catch (ClassNotFoundException err) {
183 0 : throw new InvalidPluginException(
184 0 : String.format("Cannot load %s with @Listen", def.className), err);
185 0 : }
186 :
187 0 : Listen listen = clazz.getAnnotation(Listen.class);
188 0 : if (listen != null) {
189 0 : listen(clazz, clazz);
190 : } else {
191 0 : logger.atWarning().log(
192 0 : "In plugin %s asm incorrectly parsed %s with @Listen", pluginName, clazz.getName());
193 : }
194 0 : }
195 :
196 : private void listen(java.lang.reflect.Type type, Class<?> clazz) throws InvalidPluginException {
197 0 : while (type != null) {
198 : Class<?> rawType;
199 0 : if (type instanceof ParameterizedType) {
200 0 : rawType = (Class<?>) ((ParameterizedType) type).getRawType();
201 0 : } else if (type instanceof Class) {
202 0 : rawType = (Class<?>) type;
203 : } else {
204 0 : return;
205 : }
206 :
207 0 : if (rawType.getAnnotation(ExtensionPoint.class) != null) {
208 0 : TypeLiteral<?> tl = TypeLiteral.get(type);
209 0 : if (env.hasDynamicItem(tl)) {
210 0 : sysSingletons.add(clazz);
211 0 : sysListen.put(tl, clazz);
212 0 : httpGen.listen(tl, clazz);
213 0 : sshGen.listen(tl, clazz);
214 0 : } else if (env.hasDynamicSet(tl)) {
215 0 : sysSingletons.add(clazz);
216 0 : sysListen.put(tl, clazz);
217 0 : httpGen.listen(tl, clazz);
218 0 : sshGen.listen(tl, clazz);
219 0 : } else if (env.hasDynamicMap(tl)) {
220 0 : if (clazz.getAnnotation(Export.class) == null) {
221 0 : throw new InvalidPluginException(
222 0 : String.format(
223 : "Class %s requires @Export(\"name\") annotation for %s",
224 0 : clazz.getName(), rawType.getName()));
225 : }
226 0 : sysSingletons.add(clazz);
227 0 : sysListen.put(tl, clazz);
228 0 : httpGen.listen(tl, clazz);
229 0 : sshGen.listen(tl, clazz);
230 : } else {
231 0 : throw new InvalidPluginException(
232 0 : String.format(
233 : "Cannot register %s, server does not accept %s",
234 0 : clazz.getName(), rawType.getName()));
235 : }
236 0 : return;
237 : }
238 :
239 0 : java.lang.reflect.Type[] interfaces = rawType.getGenericInterfaces();
240 0 : if (interfaces != null) {
241 0 : for (java.lang.reflect.Type i : interfaces) {
242 0 : listen(i, clazz);
243 : }
244 : }
245 :
246 0 : type = rawType.getGenericSuperclass();
247 0 : }
248 0 : }
249 : }
|