| // 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.server.plugins; |
| |
| import static com.google.gerrit.extensions.webui.JavaScriptPlugin.STATIC_INIT_JS; |
| import static com.google.gerrit.server.plugins.AutoRegisterUtil.calculateBindAnnotation; |
| import static com.google.gerrit.server.plugins.PluginGuiceEnvironment.is; |
| |
| import com.google.common.collect.LinkedListMultimap; |
| import com.google.common.collect.ListMultimap; |
| import com.google.common.flogger.FluentLogger; |
| import com.google.gerrit.extensions.annotations.Export; |
| import com.google.gerrit.extensions.annotations.ExtensionPoint; |
| import com.google.gerrit.extensions.annotations.Listen; |
| import com.google.gerrit.extensions.registration.DynamicSet; |
| import com.google.gerrit.extensions.webui.JavaScriptPlugin; |
| import com.google.gerrit.extensions.webui.WebUiPlugin; |
| import com.google.gerrit.server.plugins.PluginContentScanner.ExtensionMetaData; |
| import com.google.inject.AbstractModule; |
| import com.google.inject.Module; |
| import com.google.inject.Scopes; |
| import com.google.inject.TypeLiteral; |
| import java.io.IOException; |
| import java.lang.annotation.Annotation; |
| import java.lang.reflect.ParameterizedType; |
| import java.util.Arrays; |
| import java.util.HashSet; |
| import java.util.Map; |
| import java.util.Set; |
| |
| class AutoRegisterModules { |
| private static final FluentLogger logger = FluentLogger.forEnclosingClass(); |
| |
| private final String pluginName; |
| private final PluginGuiceEnvironment env; |
| private final PluginContentScanner scanner; |
| private final ClassLoader classLoader; |
| private final ModuleGenerator sshGen; |
| private final ModuleGenerator httpGen; |
| |
| private Set<Class<?>> sysSingletons; |
| private ListMultimap<TypeLiteral<?>, Class<?>> sysListen; |
| private String initJs; |
| |
| Module sysModule; |
| Module sshModule; |
| Module httpModule; |
| |
| AutoRegisterModules( |
| String pluginName, |
| PluginGuiceEnvironment env, |
| PluginContentScanner scanner, |
| ClassLoader classLoader) { |
| this.pluginName = pluginName; |
| this.env = env; |
| this.scanner = scanner; |
| this.classLoader = classLoader; |
| this.sshGen = env.hasSshModule() ? env.newSshModuleGenerator() : new ModuleGenerator.NOP(); |
| this.httpGen = env.hasHttpModule() ? env.newHttpModuleGenerator() : new ModuleGenerator.NOP(); |
| } |
| |
| AutoRegisterModules discover() throws InvalidPluginException { |
| sysSingletons = new HashSet<>(); |
| sysListen = LinkedListMultimap.create(); |
| initJs = null; |
| |
| sshGen.setPluginName(pluginName); |
| httpGen.setPluginName(pluginName); |
| |
| scan(); |
| |
| if (!sysSingletons.isEmpty() || !sysListen.isEmpty() || initJs != null) { |
| sysModule = makeSystemModule(); |
| } |
| sshModule = sshGen.create(); |
| httpModule = httpGen.create(); |
| return this; |
| } |
| |
| private Module makeSystemModule() { |
| return new AbstractModule() { |
| @Override |
| protected void configure() { |
| for (Class<?> clazz : sysSingletons) { |
| bind(clazz).in(Scopes.SINGLETON); |
| } |
| for (Map.Entry<TypeLiteral<?>, Class<?>> e : sysListen.entries()) { |
| @SuppressWarnings("unchecked") |
| TypeLiteral<Object> type = (TypeLiteral<Object>) e.getKey(); |
| |
| @SuppressWarnings("unchecked") |
| Class<Object> impl = (Class<Object>) e.getValue(); |
| |
| Annotation n = calculateBindAnnotation(impl); |
| bind(type).annotatedWith(n).to(impl); |
| } |
| if (initJs != null) { |
| DynamicSet.bind(binder(), WebUiPlugin.class).toInstance(new JavaScriptPlugin(initJs)); |
| } |
| } |
| }; |
| } |
| |
| private void scan() throws InvalidPluginException { |
| Map<Class<? extends Annotation>, Iterable<ExtensionMetaData>> extensions = |
| scanner.scan(pluginName, Arrays.asList(Export.class, Listen.class)); |
| for (ExtensionMetaData export : extensions.get(Export.class)) { |
| export(export); |
| } |
| for (ExtensionMetaData listener : extensions.get(Listen.class)) { |
| listen(listener); |
| } |
| if (env.hasHttpModule()) { |
| exportInitJs(); |
| } |
| } |
| |
| private void exportInitJs() { |
| try { |
| if (scanner.getEntry(STATIC_INIT_JS).isPresent()) { |
| initJs = STATIC_INIT_JS; |
| } |
| } catch (IOException e) { |
| logger.atWarning().withCause(e).log( |
| "Cannot access %s from plugin %s: " |
| + "JavaScript auto-discovered plugin will not be registered", |
| STATIC_INIT_JS, pluginName); |
| } |
| } |
| |
| private void export(ExtensionMetaData def) throws InvalidPluginException { |
| Class<?> clazz; |
| try { |
| clazz = Class.forName(def.className, false, classLoader); |
| } catch (ClassNotFoundException err) { |
| throw new InvalidPluginException( |
| String.format("Cannot load %s with @Export(\"%s\")", def.className, def.annotationValue), |
| err); |
| } |
| |
| Export export = clazz.getAnnotation(Export.class); |
| if (export == null) { |
| logger.atWarning().log( |
| "In plugin %s asm incorrectly parsed %s with @Export(\"%s\")", |
| pluginName, clazz.getName(), def.annotationValue); |
| return; |
| } |
| |
| if (is("org.apache.sshd.server.command.Command", clazz)) { |
| sshGen.export(export, clazz); |
| } else if (is("javax.servlet.http.HttpServlet", clazz)) { |
| httpGen.export(export, clazz); |
| listen(clazz, clazz); |
| } else { |
| int cnt = sysListen.size(); |
| listen(clazz, clazz); |
| if (cnt == sysListen.size()) { |
| // If no bindings were recorded, the extension isn't recognized. |
| throw new InvalidPluginException( |
| String.format( |
| "Class %s with @Export(\"%s\") not supported", clazz.getName(), export.value())); |
| } |
| } |
| } |
| |
| private void listen(ExtensionMetaData def) throws InvalidPluginException { |
| Class<?> clazz; |
| try { |
| clazz = Class.forName(def.className, false, classLoader); |
| } catch (ClassNotFoundException err) { |
| throw new InvalidPluginException( |
| String.format("Cannot load %s with @Listen", def.className), err); |
| } |
| |
| Listen listen = clazz.getAnnotation(Listen.class); |
| if (listen != null) { |
| listen(clazz, clazz); |
| } else { |
| logger.atWarning().log( |
| "In plugin %s asm incorrectly parsed %s with @Listen", pluginName, clazz.getName()); |
| } |
| } |
| |
| private void listen(java.lang.reflect.Type type, Class<?> clazz) throws InvalidPluginException { |
| while (type != null) { |
| Class<?> rawType; |
| if (type instanceof ParameterizedType) { |
| rawType = (Class<?>) ((ParameterizedType) type).getRawType(); |
| } else if (type instanceof Class) { |
| rawType = (Class<?>) type; |
| } else { |
| return; |
| } |
| |
| if (rawType.getAnnotation(ExtensionPoint.class) != null) { |
| TypeLiteral<?> tl = TypeLiteral.get(type); |
| if (env.hasDynamicItem(tl)) { |
| sysSingletons.add(clazz); |
| sysListen.put(tl, clazz); |
| httpGen.listen(tl, clazz); |
| sshGen.listen(tl, clazz); |
| } else if (env.hasDynamicSet(tl)) { |
| sysSingletons.add(clazz); |
| sysListen.put(tl, clazz); |
| httpGen.listen(tl, clazz); |
| sshGen.listen(tl, clazz); |
| } else if (env.hasDynamicMap(tl)) { |
| if (clazz.getAnnotation(Export.class) == null) { |
| throw new InvalidPluginException( |
| String.format( |
| "Class %s requires @Export(\"name\") annotation for %s", |
| clazz.getName(), rawType.getName())); |
| } |
| sysSingletons.add(clazz); |
| sysListen.put(tl, clazz); |
| httpGen.listen(tl, clazz); |
| sshGen.listen(tl, clazz); |
| } else { |
| throw new InvalidPluginException( |
| String.format( |
| "Cannot register %s, server does not accept %s", |
| clazz.getName(), rawType.getName())); |
| } |
| return; |
| } |
| |
| java.lang.reflect.Type[] interfaces = rawType.getGenericInterfaces(); |
| if (interfaces != null) { |
| for (java.lang.reflect.Type i : interfaces) { |
| listen(i, clazz); |
| } |
| } |
| |
| type = rawType.getGenericSuperclass(); |
| } |
| } |
| } |