| // 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(); | |
| } | |
| } | |
| } |